github.com/grahambrereton-form3/tilt@v0.10.18/internal/engine/build_and_deployer_test.go (about) 1 package engine 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "context" 7 "errors" 8 "fmt" 9 "path/filepath" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/docker/docker/api/types" 15 "github.com/opencontainers/go-digest" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 19 "github.com/windmilleng/wmclient/pkg/dirs" 20 21 "github.com/windmilleng/tilt/internal/build" 22 "github.com/windmilleng/tilt/internal/container" 23 "github.com/windmilleng/tilt/internal/dockercompose" 24 "github.com/windmilleng/tilt/internal/k8s/testyaml" 25 "github.com/windmilleng/tilt/internal/ospath" 26 "github.com/windmilleng/tilt/internal/store" 27 28 "github.com/windmilleng/tilt/internal/docker" 29 "github.com/windmilleng/tilt/internal/k8s" 30 "github.com/windmilleng/tilt/internal/synclet" 31 "github.com/windmilleng/tilt/internal/synclet/sidecar" 32 "github.com/windmilleng/tilt/internal/testutils" 33 "github.com/windmilleng/tilt/internal/testutils/manifestbuilder" 34 "github.com/windmilleng/tilt/internal/testutils/tempdir" 35 "github.com/windmilleng/tilt/pkg/model" 36 ) 37 38 var testImageRef = container.MustParseNamedTagged("gcr.io/some-project-162817/sancho:deadbeef") 39 var imageTargetID = model.TargetID{ 40 Type: model.TargetTypeImage, 41 Name: "gcr.io/some-project-162817/sancho", 42 } 43 44 var alreadyBuilt = store.NewImageBuildResult(imageTargetID, testImageRef) 45 var alreadyBuiltSet = store.BuildResultSet{imageTargetID: alreadyBuilt} 46 47 type expectedFile = testutils.ExpectedFile 48 49 var testPodID k8s.PodID = "pod-id" 50 var testContainerInfo = store.ContainerInfo{ 51 PodID: testPodID, 52 ContainerID: k8s.MagicTestContainerID, 53 ContainerName: "container-name", 54 } 55 56 func TestGKEDeploy(t *testing.T) { 57 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 58 defer f.TearDown() 59 60 manifest := NewSanchoLiveUpdateManifest(f) 61 targets := buildTargets(manifest) 62 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{}) 63 if err != nil { 64 t.Fatal(err) 65 } 66 67 if f.docker.BuildCount != 1 { 68 t.Errorf("Expected 1 docker build, actual: %d", f.docker.BuildCount) 69 } 70 71 if f.docker.PushCount != 1 { 72 t.Errorf("Expected 1 push to docker, actual: %d", f.docker.PushCount) 73 } 74 75 expectedYaml := "image: gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95" 76 if !strings.Contains(f.k8s.Yaml, expectedYaml) { 77 t.Errorf("Expected yaml to contain %q. Actual:\n%s", expectedYaml, f.k8s.Yaml) 78 } 79 80 if !strings.Contains(f.k8s.Yaml, sidecar.DefaultSyncletImageName) { 81 t.Errorf("Should deploy the synclet on docker-for-desktop: %s", f.k8s.Yaml) 82 } 83 } 84 85 func TestDockerForMacDeploy(t *testing.T) { 86 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 87 defer f.TearDown() 88 89 manifest := NewSanchoFastBuildManifest(f) 90 targets := buildTargets(manifest) 91 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{}) 92 if err != nil { 93 t.Fatal(err) 94 } 95 96 if f.docker.BuildCount != 1 { 97 t.Errorf("Expected 1 docker build, actual: %d", f.docker.BuildCount) 98 } 99 100 if f.docker.PushCount != 0 { 101 t.Errorf("Expected no push to docker, actual: %d", f.docker.PushCount) 102 } 103 104 expectedYaml := "image: gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95" 105 if !strings.Contains(f.k8s.Yaml, expectedYaml) { 106 t.Errorf("Expected yaml to contain %q. Actual:\n%s", expectedYaml, f.k8s.Yaml) 107 } 108 109 if strings.Contains(f.k8s.Yaml, sidecar.DefaultSyncletImageName) { 110 t.Errorf("Should not deploy the synclet on docker-for-desktop: %s", f.k8s.Yaml) 111 } 112 } 113 114 func TestNamespaceGKE(t *testing.T) { 115 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 116 defer f.TearDown() 117 118 assert.Equal(t, "", string(f.sCli.Namespace)) 119 assert.Equal(t, "", string(f.k8s.LastPodQueryNamespace)) 120 121 manifest := NewSanchoFastBuildManifest(f) 122 targets := buildTargets(manifest) 123 result, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{}) 124 if err != nil { 125 t.Fatal(err) 126 } 127 128 cInfo := testContainerInfo 129 cInfo.Namespace = "sancho-ns" 130 131 changed := f.WriteFile("a.txt", "a") 132 bs := resultToStateSet(result, []string{changed}, cInfo) 133 result, err = f.bd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), bs) 134 if err != nil { 135 t.Fatal(err) 136 } 137 138 assert.Equal(t, "sancho-ns", string(f.sCli.Namespace)) 139 } 140 141 func TestYamlManifestDeploy(t *testing.T) { 142 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 143 defer f.TearDown() 144 145 manifest := manifestbuilder.New(f, "some_yaml"). 146 WithK8sYAML(testyaml.TracerYAML).Build() 147 targets := buildTargets(manifest) 148 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{}) 149 if err != nil { 150 t.Fatal(err) 151 } 152 153 assert.Equal(t, 0, f.sCli.UpdateContainerCount) 154 assert.Equal(t, 0, f.docker.BuildCount) 155 assert.Equal(t, 0, f.docker.PushCount) 156 f.assertK8sUpsertCalled(true) 157 } 158 159 func TestContainerBuildLocal(t *testing.T) { 160 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 161 defer f.TearDown() 162 163 changed := f.WriteFile("a.txt", "a") 164 manifest := NewSanchoFastBuildManifest(f) 165 targets := buildTargets(manifest) 166 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 167 result, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 168 if err != nil { 169 t.Fatal(err) 170 } 171 172 if f.docker.BuildCount != 0 { 173 t.Errorf("Expected no docker build, actual: %d", f.docker.BuildCount) 174 } 175 if f.docker.PushCount != 0 { 176 t.Errorf("Expected no push to docker, actual: %d", f.docker.PushCount) 177 } 178 if f.docker.CopyCount != 1 { 179 t.Errorf("Expected 1 copy to docker container call, actual: %d", f.docker.CopyCount) 180 } 181 if len(f.docker.ExecCalls) != 1 { 182 t.Errorf("Expected 1 exec in container call, actual: %d", len(f.docker.ExecCalls)) 183 } 184 f.assertContainerRestarts(1) 185 186 id := manifest.ImageTargetAt(0).ID() 187 _, hasResult := result[id] 188 assert.True(t, hasResult) 189 assert.Equal(t, k8s.MagicTestContainerID, result.OneAndOnlyLiveUpdatedContainerID().String()) 190 } 191 192 func TestContainerBuildSynclet(t *testing.T) { 193 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 194 defer f.TearDown() 195 196 changed := f.WriteFile("a.txt", "a") 197 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 198 manifest := NewSanchoFastBuildManifest(f) 199 targets := buildTargets(manifest) 200 result, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 201 if err != nil { 202 t.Fatal(err) 203 } 204 205 assert.Equal(t, 0, f.docker.BuildCount, 206 "Expected no docker build, actual: %d", f.docker.BuildCount) 207 assert.Equal(t, 0, f.docker.PushCount, 208 "Expected no push to docker, actual: %d", f.docker.PushCount) 209 assert.Equal(t, 1, f.sCli.UpdateContainerCount, 210 "Expected 1 synclet UpdateContainer call, actual: %d", f.sCli.UpdateContainerCount) 211 assert.Equal(t, 1, f.docker.CopyCount, 212 "Expected 1 docker CopyToContainer (via synclet), actual: %d", f.docker.CopyCount) 213 assert.Len(t, f.docker.ExecCalls, 1, 214 "Expected 1 docker Exec call (via synclet), actual: %d", len(f.docker.ExecCalls)) 215 f.assertContainerRestarts(1) 216 assert.Equal(t, k8s.MagicTestContainerID, result.OneAndOnlyLiveUpdatedContainerID().String()) 217 } 218 219 func TestContainerBuildLocalTriggeredRuns(t *testing.T) { 220 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 221 defer f.TearDown() 222 223 changed := f.WriteFile("a.txt", "a") 224 manifest := NewSanchoFastBuildManifest(f) 225 iTarg := manifest.ImageTargetAt(0) 226 fb := iTarg.TopFastBuildInfo() 227 runs := []model.Run{ 228 model.Run{Cmd: model.ToShellCmd("echo hello")}, 229 model.Run{Cmd: model.ToShellCmd("echo a"), Triggers: f.NewPathSet("a.txt")}, // matches changed file 230 model.Run{Cmd: model.ToShellCmd("echo b"), Triggers: f.NewPathSet("b.txt")}, // does NOT match changed file 231 } 232 fb.Runs = runs 233 iTarg = iTarg.WithBuildDetails(fb) 234 manifest = manifest.WithImageTarget(iTarg) 235 236 targets := buildTargets(manifest) 237 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 238 result, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 239 if err != nil { 240 t.Fatal(err) 241 } 242 243 if f.docker.BuildCount != 0 { 244 t.Errorf("Expected no docker build, actual: %d", f.docker.BuildCount) 245 } 246 if f.docker.PushCount != 0 { 247 t.Errorf("Expected no push to docker, actual: %d", f.docker.PushCount) 248 } 249 if f.docker.CopyCount != 1 { 250 t.Errorf("Expected 1 copy to docker container call, actual: %d", f.docker.CopyCount) 251 } 252 if len(f.docker.ExecCalls) != 2 { 253 t.Errorf("Expected 2 exec in container calls, actual: %d", len(f.docker.ExecCalls)) 254 } 255 f.assertContainerRestarts(1) 256 257 id := manifest.ImageTargetAt(0).ID() 258 _, hasResult := result[id] 259 assert.True(t, hasResult) 260 assert.Equal(t, k8s.MagicTestContainerID, result.OneAndOnlyLiveUpdatedContainerID().String()) 261 } 262 263 func TestContainerBuildSyncletTriggeredRuns(t *testing.T) { 264 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 265 defer f.TearDown() 266 267 changed := f.WriteFile("a.txt", "a") 268 manifest := NewSanchoFastBuildManifest(f) 269 iTarg := manifest.ImageTargetAt(0) 270 fb := iTarg.TopFastBuildInfo() 271 runs := []model.Run{ 272 model.Run{Cmd: model.ToShellCmd("echo hello")}, 273 model.Run{Cmd: model.ToShellCmd("echo a"), Triggers: f.NewPathSet("a.txt")}, // matches changed file 274 model.Run{Cmd: model.ToShellCmd("echo b"), Triggers: f.NewPathSet("b.txt")}, // does NOT match changed file 275 } 276 fb.Runs = runs 277 iTarg = iTarg.WithBuildDetails(fb) 278 manifest = manifest.WithImageTarget(iTarg) 279 280 targets := buildTargets(manifest) 281 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 282 result, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 283 if err != nil { 284 t.Fatal(err) 285 } 286 287 assert.Equal(t, 0, f.docker.BuildCount, "Expected no docker build, actual: %d", f.docker.BuildCount) 288 assert.Equal(t, 0, f.docker.PushCount, "Expected no push to docker, actual: %d", f.docker.PushCount) 289 assert.Equal(t, 1, f.sCli.UpdateContainerCount, 290 "Expected 1 synclet UpdateContainer call, actual: %d", f.sCli.UpdateContainerCount) 291 assert.Equal(t, 1, f.docker.CopyCount, 292 "Expected 1 docker CopyToContainer (via synclet), actual: %d", f.docker.CopyCount) 293 assert.Len(t, f.docker.ExecCalls, 2, 294 "Expected 1 docker Exec call (via synclet), actual: %d", len(f.docker.ExecCalls)) 295 f.assertContainerRestarts(1) 296 assert.Equal(t, k8s.MagicTestContainerID, result.OneAndOnlyLiveUpdatedContainerID().String()) 297 } 298 299 func TestContainerBuildSyncletHotReload(t *testing.T) { 300 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 301 defer f.TearDown() 302 303 changed := f.WriteFile("a.txt", "a") 304 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 305 manifest := NewSanchoFastBuildManifest(f) 306 iTarget := manifest.ImageTargetAt(0) 307 fbInfo := iTarget.TopFastBuildInfo() 308 fbInfo.HotReload = true 309 manifest = manifest.WithImageTarget(iTarget.WithBuildDetails(fbInfo)) 310 targets := buildTargets(manifest) 311 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 312 if err != nil { 313 t.Fatal(err) 314 } 315 assert.Equal(t, 1, f.sCli.UpdateContainerCount, 316 "Expected 1 synclet UpdateContainer call, actual: %d", f.sCli.UpdateContainerCount) 317 f.assertContainerRestarts(0) 318 } 319 320 func TestDockerBuildWithNestedFastBuildDeploysSynclet(t *testing.T) { 321 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 322 defer f.TearDown() 323 324 manifest := NewSanchoDockerBuildManifestWithNestedFastBuild(f) 325 targets := buildTargets(manifest) 326 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{}) 327 if err != nil { 328 t.Fatal(err) 329 } 330 331 if f.docker.BuildCount != 1 { 332 t.Errorf("Expected 1 docker build, actual: %d", f.docker.BuildCount) 333 } 334 335 if f.docker.PushCount != 1 { 336 t.Errorf("Expected 1 docker push, actual: %d", f.docker.PushCount) 337 } 338 339 expectedYaml := "image: gcr.io/some-project-162817/sancho:tilt-11cd0b38bc3ceb95" 340 if !strings.Contains(f.k8s.Yaml, expectedYaml) { 341 t.Errorf("Expected yaml to contain %q. Actual:\n%s", expectedYaml, f.k8s.Yaml) 342 } 343 344 if !strings.Contains(f.k8s.Yaml, sidecar.DefaultSyncletImageName) { 345 t.Errorf("Should deploy the synclet for a docker build with a nested fast build: %s", f.k8s.Yaml) 346 } 347 } 348 349 func TestDockerBuildWithNestedFastBuildContainerUpdate(t *testing.T) { 350 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 351 defer f.TearDown() 352 353 changed := f.WriteFile("a.txt", "a") 354 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 355 manifest := NewSanchoDockerBuildManifestWithNestedFastBuild(f) 356 targets := buildTargets(manifest) 357 result, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 358 if err != nil { 359 t.Fatal(err) 360 } 361 362 if f.docker.BuildCount != 0 { 363 t.Errorf("Expected no docker build, actual: %d", f.docker.BuildCount) 364 } 365 if f.docker.PushCount != 0 { 366 t.Errorf("Expected no push to docker, actual: %d", f.docker.PushCount) 367 } 368 if f.docker.CopyCount != 1 { 369 t.Errorf("Expected 1 copy to docker container call, actual: %d", f.docker.CopyCount) 370 } 371 if len(f.docker.ExecCalls) != 1 { 372 t.Errorf("Expected 1 exec in container call, actual: %d", len(f.docker.ExecCalls)) 373 } 374 f.assertContainerRestarts(1) 375 376 id := manifest.ImageTargetAt(0).ID() 377 _, hasResult := result[id] 378 assert.True(t, hasResult) 379 assert.Equal(t, k8s.MagicTestContainerID, result.OneAndOnlyLiveUpdatedContainerID().String()) 380 } 381 382 func TestIncrementalBuildFailure(t *testing.T) { 383 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 384 defer f.TearDown() 385 386 changed := f.WriteFile("a.txt", "a") 387 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 388 f.docker.SetExecError(docker.ExitError{ExitCode: 1}) 389 390 manifest := NewSanchoFastBuildManifest(f) 391 targets := buildTargets(manifest) 392 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 393 msg := "Run step \"go install github.com/windmilleng/sancho\" failed with exit code: 1" 394 if err == nil || !strings.Contains(err.Error(), msg) { 395 t.Fatalf("Expected error message %q, actual: %v", msg, err) 396 } 397 398 if f.docker.BuildCount != 0 { 399 t.Errorf("Expected no docker build, actual: %d", f.docker.BuildCount) 400 } 401 402 if f.docker.PushCount != 0 { 403 t.Errorf("Expected no push to docker, actual: %d", f.docker.PushCount) 404 } 405 if f.docker.CopyCount != 1 { 406 t.Errorf("Expected 1 copy to docker container call, actual: %d", f.docker.CopyCount) 407 } 408 if len(f.docker.ExecCalls) != 1 { 409 t.Errorf("Expected 1 exec in container call, actual: %d", len(f.docker.ExecCalls)) 410 } 411 f.assertContainerRestarts(0) 412 } 413 414 func TestIncrementalBuildKilled(t *testing.T) { 415 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 416 defer f.TearDown() 417 418 changed := f.WriteFile("a.txt", "a") 419 420 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 421 f.docker.SetExecError(docker.ExitError{ExitCode: build.TaskKillExitCode}) 422 423 manifest := NewSanchoFastBuildManifest(f) 424 targets := buildTargets(manifest) 425 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 426 if err != nil { 427 t.Fatal(err) 428 } 429 430 assert.Equal(t, 1, f.docker.CopyCount) 431 assert.Equal(t, 1, len(f.docker.ExecCalls)) 432 433 // Falls back to a build when the exec fails 434 assert.Equal(t, 1, f.docker.BuildCount) 435 } 436 437 func TestFallBackToImageDeploy(t *testing.T) { 438 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 439 defer f.TearDown() 440 441 f.docker.SetExecError(errors.New("some random error")) 442 443 changed := f.WriteFile("a.txt", "a") 444 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 445 446 manifest := NewSanchoFastBuildManifest(f) 447 targets := buildTargets(manifest) 448 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 449 if err != nil { 450 t.Fatal(err) 451 } 452 453 f.assertContainerRestarts(0) 454 if f.docker.BuildCount != 1 { 455 t.Errorf("Expected 1 docker build, actual: %d", f.docker.BuildCount) 456 } 457 } 458 459 func TestNoFallbackForDontFallBackError(t *testing.T) { 460 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 461 defer f.TearDown() 462 f.docker.SetExecError(DontFallBackErrorf("i'm melllting")) 463 464 changed := f.WriteFile("a.txt", "a") 465 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 466 467 manifest := NewSanchoFastBuildManifest(f) 468 targets := buildTargets(manifest) 469 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 470 if err == nil { 471 t.Errorf("Expected this error to fail fallback tester and propogate back up") 472 } 473 474 if f.docker.BuildCount != 0 { 475 t.Errorf("Expected no docker build, actual: %d", f.docker.BuildCount) 476 } 477 478 if f.docker.PushCount != 0 { 479 t.Errorf("Expected no push to docker, actual: %d", f.docker.PushCount) 480 } 481 } 482 483 func TestIncrementalBuildTwice(t *testing.T) { 484 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 485 defer f.TearDown() 486 487 manifest := NewSanchoFastBuildManifest(f) 488 targets := buildTargets(manifest) 489 aPath := f.WriteFile("a.txt", "a") 490 bPath := f.WriteFile("b.txt", "b") 491 492 firstState := resultToStateSet(alreadyBuiltSet, []string{aPath}, testContainerInfo) 493 firstResult, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, firstState) 494 if err != nil { 495 t.Fatal(err) 496 } 497 498 secondState := resultToStateSet(firstResult, []string{bPath}, testContainerInfo) 499 _, err = f.bd.BuildAndDeploy(f.ctx, f.st, targets, secondState) 500 if err != nil { 501 t.Fatal(err) 502 } 503 504 if f.docker.BuildCount != 0 { 505 t.Errorf("Expected no docker build, actual: %d", f.docker.BuildCount) 506 } 507 if f.docker.PushCount != 0 { 508 t.Errorf("Expected no push to docker, actual: %d", f.docker.PushCount) 509 } 510 if f.docker.CopyCount != 2 { 511 t.Errorf("Expected 2 copy to docker container call, actual: %d", f.docker.CopyCount) 512 } 513 if len(f.docker.ExecCalls) != 2 { 514 t.Errorf("Expected 2 exec in container call, actual: %d", len(f.docker.ExecCalls)) 515 } 516 f.assertContainerRestarts(2) 517 } 518 519 // Kill the pod after the first container update, 520 // and make sure the next image build gets the right file updates. 521 func TestIncrementalBuildTwiceDeadPod(t *testing.T) { 522 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 523 defer f.TearDown() 524 525 manifest := NewSanchoFastBuildManifest(f) 526 targets := buildTargets(manifest) 527 aPath := f.WriteFile("a.txt", "a") 528 bPath := f.WriteFile("b.txt", "b") 529 530 firstState := resultToStateSet(alreadyBuiltSet, []string{aPath}, testContainerInfo) 531 firstResult, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, firstState) 532 if err != nil { 533 t.Fatal(err) 534 } 535 536 // Kill the pod 537 f.docker.SetExecError(fmt.Errorf("Dead pod")) 538 539 secondState := resultToStateSet(firstResult, []string{bPath}, testContainerInfo) 540 _, err = f.bd.BuildAndDeploy(f.ctx, f.st, targets, secondState) 541 if err != nil { 542 t.Fatal(err) 543 } 544 545 if f.docker.BuildCount != 1 { 546 t.Errorf("Expected 1 docker build, actual: %d", f.docker.BuildCount) 547 } 548 if f.docker.PushCount != 0 { 549 t.Errorf("Expected 0 pushes to docker, actual: %d", f.docker.PushCount) 550 } 551 if f.docker.CopyCount != 2 { 552 t.Errorf("Expected 2 copy to docker container call, actual: %d", f.docker.CopyCount) 553 } 554 if len(f.docker.ExecCalls) != 2 { 555 t.Errorf("Expected 2 exec in container call, actual: %d", len(f.docker.ExecCalls)) 556 } 557 f.assertContainerRestarts(1) 558 } 559 560 func TestIgnoredFiles(t *testing.T) { 561 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 562 defer f.TearDown() 563 564 manifest := NewSanchoFastBuildManifest(f) 565 566 tiltfile := filepath.Join(f.Path(), "Tiltfile") 567 manifest = manifest.WithImageTarget(manifest.ImageTargetAt(0).WithRepos([]model.LocalGitRepo{ 568 model.LocalGitRepo{ 569 LocalPath: f.Path(), 570 }, 571 }).WithTiltFilename(tiltfile)) 572 573 f.WriteFile("Tiltfile", "# hello world") 574 f.WriteFile("a.txt", "a") 575 f.WriteFile(".git/index", "garbage") 576 577 targets := buildTargets(manifest) 578 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{}) 579 if err != nil { 580 t.Fatal(err) 581 } 582 583 tr := tar.NewReader(f.docker.BuildOptions.Context) 584 testutils.AssertFilesInTar(t, tr, []expectedFile{ 585 expectedFile{ 586 Path: "go/src/github.com/windmilleng/sancho/a.txt", 587 Contents: "a", 588 }, 589 expectedFile{ 590 Path: "go/src/github.com/windmilleng/sancho/.git/index", 591 Missing: true, 592 }, 593 expectedFile{ 594 Path: "go/src/github.com/windmilleng/sancho/Tiltfile", 595 Missing: true, 596 }, 597 }) 598 } 599 600 func TestCustomBuildWithFastBuild(t *testing.T) { 601 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 602 defer f.TearDown() 603 sha := digest.Digest("sha256:11cd0eb38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab") 604 f.docker.Images["gcr.io/some-project-162817/sancho:tilt-build-1551202573"] = types.ImageInspect{ID: string(sha)} 605 606 manifest := NewSanchoCustomBuildManifestWithFastBuild(f) 607 targets := buildTargets(manifest) 608 609 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{}) 610 if err != nil { 611 t.Fatal(err) 612 } 613 614 if f.docker.BuildCount != 0 { 615 t.Errorf("Expected 0 docker build, actual: %d", f.docker.BuildCount) 616 } 617 618 if f.docker.PushCount != 1 { 619 t.Errorf("Expected 1 push to docker, actual: %d", f.docker.PushCount) 620 } 621 622 if !strings.Contains(f.k8s.Yaml, sidecar.DefaultSyncletImageName) { 623 t.Errorf("Should deploy the synclet for a custom build with a fast build: %s", f.k8s.Yaml) 624 } 625 } 626 627 func TestCustomBuildWithoutFastBuild(t *testing.T) { 628 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 629 defer f.TearDown() 630 sha := digest.Digest("sha256:11cd0eb38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab") 631 f.docker.Images["gcr.io/some-project-162817/sancho:tilt-build-1551202573"] = types.ImageInspect{ID: string(sha)} 632 633 manifest := NewSanchoCustomBuildManifest(f) 634 targets := buildTargets(manifest) 635 636 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{}) 637 if err != nil { 638 t.Fatal(err) 639 } 640 641 if f.docker.BuildCount != 0 { 642 t.Errorf("Expected 0 docker build, actual: %d", f.docker.BuildCount) 643 } 644 645 if f.docker.PushCount != 1 { 646 t.Errorf("Expected 1 push to docker, actual: %d", f.docker.PushCount) 647 } 648 649 if strings.Contains(f.k8s.Yaml, sidecar.DefaultSyncletImageName) { 650 t.Errorf("Should not deploy the synclet for a custom build: %s", f.k8s.Yaml) 651 } 652 } 653 654 func TestCustomBuildDeterministicTag(t *testing.T) { 655 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 656 defer f.TearDown() 657 refStr := "gcr.io/some-project-162817/sancho:deterministic-tag" 658 sha := digest.Digest("sha256:11cd0eb38bc3ceb958ffb2f9bd70be3fb317ce7d255c8a4c3f4af30e298aa1aab") 659 f.docker.Images[refStr] = types.ImageInspect{ID: string(sha)} 660 661 manifest := NewSanchoCustomBuildManifestWithTag(f, "deterministic-tag") 662 targets := buildTargets(manifest) 663 664 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{}) 665 if err != nil { 666 t.Fatal(err) 667 } 668 669 if f.docker.BuildCount != 0 { 670 t.Errorf("Expected 0 docker build, actual: %d", f.docker.BuildCount) 671 } 672 673 if f.docker.PushCount != 1 { 674 t.Errorf("Expected 1 push to docker, actual: %d", f.docker.PushCount) 675 } 676 677 if strings.Contains(f.k8s.Yaml, sidecar.DefaultSyncletImageName) { 678 t.Errorf("Should not deploy the synclet for a custom build: %s", f.k8s.Yaml) 679 } 680 } 681 682 func TestContainerBuildMultiStage(t *testing.T) { 683 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 684 defer f.TearDown() 685 686 manifest := NewSanchoLiveUpdateMultiStageManifest(f) 687 targets := buildTargets(manifest) 688 changed := f.WriteFile("a.txt", "a") 689 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 690 691 // There are two image targets. The first has a build result, 692 // the second does not --> second target needs build 693 iTargetID := targets[0].ID() 694 firstResult := store.NewImageBuildResult(iTargetID, container.MustParseNamedTagged("sancho-base:tilt-prebuilt")) 695 bs[iTargetID] = store.NewBuildState(firstResult, nil) 696 697 result, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 698 if err != nil { 699 t.Fatal(err) 700 } 701 702 // Docker Build/Push would imply an image build. Make sure they didn't happen, 703 // i.e. that we did a LiveUpdate 704 assert.Equal(t, 0, f.docker.BuildCount) 705 assert.Equal(t, 0, f.docker.PushCount) 706 707 // Make sure we did a LiveUpdate (copy files to container, exec in container, restart) 708 assert.Equal(t, 1, f.docker.CopyCount) 709 assert.Equal(t, 1, len(f.docker.ExecCalls)) 710 f.assertContainerRestarts(1) 711 712 // The BuildComplete action handler expects to get exactly one result 713 _, hasResult0 := result[manifest.ImageTargetAt(0).ID()] 714 assert.False(t, hasResult0) 715 _, hasResult1 := result[manifest.ImageTargetAt(1).ID()] 716 assert.True(t, hasResult1) 717 assert.Equal(t, k8s.MagicTestContainerID, result.OneAndOnlyLiveUpdatedContainerID().String()) 718 } 719 720 func TestDockerComposeImageBuild(t *testing.T) { 721 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 722 defer f.TearDown() 723 724 manifest := NewSanchoFastBuildDCManifest(f) 725 targets := buildTargets(manifest) 726 727 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, store.BuildStateSet{}) 728 if err != nil { 729 t.Fatal(err) 730 } 731 732 assert.Equal(t, 1, f.docker.BuildCount) 733 assert.Equal(t, 0, f.docker.PushCount) 734 if strings.Contains(f.k8s.Yaml, sidecar.DefaultSyncletImageName) { 735 t.Errorf("Should not deploy the synclet for a docker-compose build: %s", f.k8s.Yaml) 736 } 737 assert.Len(t, f.dcCli.UpCalls, 1) 738 } 739 740 func TestDockerComposeInPlaceUpdate(t *testing.T) { 741 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 742 defer f.TearDown() 743 744 manifest := NewSanchoFastBuildDCManifest(f) 745 targets := buildTargets(manifest) 746 changed := f.WriteFile("a.txt", "a") 747 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 748 749 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 750 if err != nil { 751 t.Fatal(err) 752 } 753 754 assert.Equal(t, 0, f.docker.BuildCount) 755 assert.Equal(t, 0, f.docker.PushCount) 756 assert.Equal(t, 1, f.docker.CopyCount) 757 assert.Equal(t, 1, len(f.docker.ExecCalls)) 758 assert.Equal(t, 0, f.sCli.UpdateContainerCount, 759 "Expected no synclet UpdateContainer call, actual: %d", f.sCli.UpdateContainerCount) 760 if strings.Contains(f.k8s.Yaml, sidecar.DefaultSyncletImageName) { 761 t.Errorf("Should not deploy the synclet for a docker-compose build: %s", f.k8s.Yaml) 762 } 763 f.assertContainerRestarts(1) 764 } 765 766 func TestReturnLastUnexpectedError(t *testing.T) { 767 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 768 defer f.TearDown() 769 770 // next Docker build will throw an unexpected error -- this is one we want to return, 771 // even if subsequent builders throw expected errors. 772 f.docker.BuildErrorToThrow = fmt.Errorf("no one expects the unexpected error") 773 774 manifest := NewSanchoFastBuildDCManifest(f) 775 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), store.BuildStateSet{}) 776 if assert.Error(t, err) { 777 assert.Contains(t, err.Error(), "no one expects the unexpected error") 778 } 779 } 780 781 func TestLiveUpdateWithRunFailureReturnsContainerIDs(t *testing.T) { 782 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 783 defer f.TearDown() 784 785 // LiveUpdate will failure with a RunStepFailure 786 f.docker.SetExecError(userFailureErrDocker) 787 788 manifest := NewSanchoLiveUpdateManifest(f) 789 targets := buildTargets(manifest) 790 changed := f.WriteFile("a.txt", "a") 791 bs := resultToStateSet(alreadyBuiltSet, []string{changed}, testContainerInfo) 792 resultSet, err := f.bd.BuildAndDeploy(f.ctx, f.st, targets, bs) 793 require.NotNil(t, err, "expected failed LiveUpdate to return error") 794 795 iTargID := manifest.ImageTargetAt(0).ID() 796 result := resultSet[iTargID] 797 res, ok := result.(store.LiveUpdateBuildResult) 798 require.True(t, ok, "expected build result for image target %s", iTargID) 799 require.Len(t, res.LiveUpdatedContainerIDs, 1) 800 require.Equal(t, res.LiveUpdatedContainerIDs[0].String(), k8s.MagicTestContainerID) 801 802 // LiveUpdate failed due to RunStepError, should NOT fall back to image build 803 assert.Equal(t, 0, f.docker.BuildCount, "expect no image build -> no docker build calls") 804 f.assertK8sUpsertCalled(false) 805 806 // Copied files and tried to docker.exec before hitting error 807 assert.Equal(t, 1, f.docker.CopyCount) 808 assert.Equal(t, 1, len(f.docker.ExecCalls)) 809 } 810 811 func TestLiveUpdateMultipleImagesSamePod(t *testing.T) { 812 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 813 defer f.TearDown() 814 815 manifest, bs := multiImageLiveUpdateManifestAndBuildState(f) 816 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), bs) 817 if err != nil { 818 t.Fatal(err) 819 } 820 821 // expect live update and NOT an image build 822 assert.Equal(t, 0, f.docker.BuildCount) 823 assert.Equal(t, 0, f.docker.PushCount) 824 f.assertK8sUpsertCalled(false) 825 826 // one for each container update 827 assert.Equal(t, 2, f.sCli.UpdateContainerCount) 828 } 829 830 func TestOneLiveUpdateOneDockerBuildDoesImageBuild(t *testing.T) { 831 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 832 defer f.TearDown() 833 834 sanchoTarg := NewSanchoLiveUpdateImageTarget(f) // first target = LiveUpdate 835 sidecarTarg := NewSanchoSidecarDockerBuildImageTarget(f) // second target = DockerBuild 836 sanchoRef := container.MustParseNamedTagged(fmt.Sprintf("%s:tilt-123", testyaml.SanchoImage)) 837 sidecarRef := container.MustParseNamedTagged(fmt.Sprintf("%s:tilt-123", testyaml.SanchoSidecarImage)) 838 sanchoCInfo := store.ContainerInfo{ 839 PodID: testPodID, 840 ContainerName: "sancho", 841 ContainerID: "sancho-c", 842 } 843 844 manifest := manifestbuilder.New(f, "sancho"). 845 WithK8sYAML(testyaml.SanchoSidecarYAML). 846 WithImageTargets(sanchoTarg, sidecarTarg). 847 Build() 848 changed := f.WriteFile("a.txt", "a") 849 sanchoState := store.NewBuildState(store.NewImageBuildResult(sanchoTarg.ID(), sanchoRef), []string{changed}). 850 WithRunningContainers([]store.ContainerInfo{sanchoCInfo}) 851 sidecarState := store.NewBuildState(store.NewImageBuildResult(sidecarTarg.ID(), sidecarRef), []string{changed}) 852 853 bs := store.BuildStateSet{sanchoTarg.ID(): sanchoState, sidecarTarg.ID(): sidecarState} 854 855 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), bs) 856 if err != nil { 857 t.Fatal(err) 858 } 859 860 // expect an image build 861 assert.Equal(t, 2, f.docker.BuildCount) 862 assert.Equal(t, 2, f.docker.PushCount) 863 f.assertK8sUpsertCalled(true) 864 865 // should NOT have run live update 866 assert.Equal(t, 0, f.sCli.UpdateContainerCount) 867 } 868 869 func TestLiveUpdateMultipleImagesOneRunErrorExecutesRestOfLiveUpdatesAndDoesntImageBuild(t *testing.T) { 870 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 871 defer f.TearDown() 872 873 // First LiveUpdate will simulate a failed Run step 874 f.docker.ExecErrorsToThrow = []error{userFailureErrDocker} 875 876 manifest, bs := multiImageLiveUpdateManifestAndBuildState(f) 877 result, err := f.bd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), bs) 878 require.NotNil(t, err) 879 assert.Contains(t, err.Error(), "Run step \"go install github.com/windmilleng/sancho\" failed with exit code: 123") 880 881 // one for each container update 882 assert.Equal(t, 2, f.docker.CopyCount) 883 assert.Equal(t, 2, len(f.docker.ExecCalls)) 884 885 // expect NO image build 886 assert.Equal(t, 0, f.docker.BuildCount) 887 assert.Equal(t, 0, f.docker.PushCount) 888 f.assertK8sUpsertCalled(false) 889 890 // Make sure we returned the CIDs we LiveUpdated -- 891 // they contain state now, we'll want to track them 892 liveUpdatedCIDs := result.LiveUpdatedContainerIDs() 893 expectedCIDs := []container.ID{"sancho-c", "sidecar-c"} 894 assert.ElementsMatch(t, expectedCIDs, liveUpdatedCIDs) 895 } 896 897 func TestLiveUpdateMultipleImagesOneUpdateErrorFallsBackToImageBuild(t *testing.T) { 898 f := newBDFixture(t, k8s.EnvDockerDesktop, container.RuntimeDocker) 899 defer f.TearDown() 900 901 // Second LiveUpdate will throw an error 902 f.docker.ExecErrorsToThrow = []error{nil, fmt.Errorf("whelp ¯\\_(ツ)_/¯")} 903 904 manifest, bs := multiImageLiveUpdateManifestAndBuildState(f) 905 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), bs) 906 if err != nil { 907 t.Fatal(err) 908 } 909 910 // one for each container update 911 assert.Equal(t, 2, f.docker.CopyCount) 912 assert.Equal(t, 2, len(f.docker.ExecCalls)) // second one errors 913 914 // expect image build (2x images) when we fall back from failed LiveUpdate 915 assert.Equal(t, 2, f.docker.BuildCount) 916 assert.Equal(t, 0, f.docker.PushCount) 917 f.assertK8sUpsertCalled(true) 918 } 919 920 func TestLiveUpdateMultipleImagesOneWithUnsyncedChangeFileFallsBackToImageBuild(t *testing.T) { 921 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 922 defer f.TearDown() 923 924 manifest, bs := multiImageLiveUpdateManifestAndBuildState(f) 925 bs[manifest.ImageTargetAt(1).ID()].FilesChangedSet["/not/synced"] = true // changed file not in a sync --> fall back to image build 926 _, err := f.bd.BuildAndDeploy(f.ctx, f.st, buildTargets(manifest), bs) 927 if err != nil { 928 t.Fatal(err) 929 } 930 931 // should NOT have run live update 932 assert.Equal(t, 0, f.sCli.UpdateContainerCount) 933 934 // expect image build (2x images) when we fall back from failed LiveUpdate 935 assert.Equal(t, 2, f.docker.BuildCount) 936 assert.Equal(t, 2, f.docker.PushCount) 937 f.assertK8sUpsertCalled(true) 938 } 939 940 func TestLocalTargetDeploy(t *testing.T) { 941 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 942 defer f.TearDown() 943 944 lt := model.LocalTarget{Cmd: model.ToShellCmd("echo hello world")} 945 res, err := f.bd.BuildAndDeploy(f.ctx, f.st, []model.TargetSpec{lt}, store.BuildStateSet{}) 946 require.Nil(t, err) 947 948 assert.Equal(t, 0, f.docker.BuildCount, "should have 0 docker builds") 949 assert.Equal(t, 0, f.docker.PushCount, "should have 0 docker pushes") 950 assert.Empty(t, f.k8s.Yaml, "should not apply any k8s yaml") 951 assert.Len(t, res, 1, "expect exactly one result in result set") 952 assert.Contains(t, f.logs.String(), "hello world", "logs should contain cmd output") 953 } 954 955 func TestLocalTargetFailure(t *testing.T) { 956 f := newBDFixture(t, k8s.EnvGKE, container.RuntimeDocker) 957 defer f.TearDown() 958 959 lt := model.LocalTarget{Cmd: model.ToShellCmd("echo oh no; false")} 960 res, err := f.bd.BuildAndDeploy(f.ctx, f.st, []model.TargetSpec{lt}, store.BuildStateSet{}) 961 assert.Empty(t, res, "expect empty result for failed command") 962 963 require.NotNil(t, err) 964 assert.Contains(t, err.Error(), "exit status 1", "error msg should indicate command failure") 965 assert.Contains(t, f.logs.String(), "oh no", "logs should contain cmd output") 966 967 assert.Equal(t, 0, f.docker.BuildCount, "should have 0 docker builds") 968 assert.Equal(t, 0, f.docker.PushCount, "should have 0 docker pushes") 969 assert.Empty(t, f.k8s.Yaml, "should not apply any k8s yaml") 970 } 971 972 func multiImageLiveUpdateManifestAndBuildState(f *bdFixture) (model.Manifest, store.BuildStateSet) { 973 sanchoTarg := NewSanchoLiveUpdateImageTarget(f) 974 sidecarTarg := NewSanchoSidecarLiveUpdateImageTarget(f) 975 sanchoRef := container.MustParseNamedTagged(fmt.Sprintf("%s:tilt-123", testyaml.SanchoImage)) 976 sidecarRef := container.MustParseNamedTagged(fmt.Sprintf("%s:tilt-123", testyaml.SanchoSidecarImage)) 977 sanchoCInfo := store.ContainerInfo{ 978 PodID: testPodID, 979 ContainerName: "sancho", 980 ContainerID: "sancho-c", 981 } 982 sidecarCInfo := store.ContainerInfo{ 983 PodID: testPodID, 984 ContainerName: "sancho-sidecar", 985 ContainerID: "sidecar-c", 986 } 987 988 manifest := manifestbuilder.New(f, "sancho"). 989 WithK8sYAML(testyaml.SanchoSidecarYAML). 990 WithImageTargets(sanchoTarg, sidecarTarg). 991 Build() 992 993 changed := f.WriteFile("a.txt", "a") 994 sanchoState := store.NewBuildState(store.NewImageBuildResult(sanchoTarg.ID(), sanchoRef), []string{changed}). 995 WithRunningContainers([]store.ContainerInfo{sanchoCInfo}) 996 sidecarState := store.NewBuildState(store.NewImageBuildResult(sidecarTarg.ID(), sidecarRef), []string{changed}). 997 WithRunningContainers([]store.ContainerInfo{sidecarCInfo}) 998 999 bs := store.BuildStateSet{sanchoTarg.ID(): sanchoState, sidecarTarg.ID(): sidecarState} 1000 1001 return manifest, bs 1002 } 1003 1004 // The API boundaries between BuildAndDeployer and the ImageBuilder aren't obvious and 1005 // are likely to change in the future. So we test them together, using 1006 // a fake Client and K8sClient 1007 type bdFixture struct { 1008 *tempdir.TempDirFixture 1009 ctx context.Context 1010 cancel func() 1011 docker *docker.FakeClient 1012 k8s *k8s.FakeK8sClient 1013 sCli *synclet.TestSyncletClient 1014 bd BuildAndDeployer 1015 st *store.Store 1016 dcCli *dockercompose.FakeDCClient 1017 logs *bytes.Buffer 1018 } 1019 1020 func newBDFixture(t *testing.T, env k8s.Env, runtime container.Runtime) *bdFixture { 1021 logs := new(bytes.Buffer) 1022 ctx, _, ta := testutils.ForkedCtxAndAnalyticsForTest(logs) 1023 ctx, cancel := context.WithCancel(ctx) 1024 f := tempdir.NewTempDirFixture(t) 1025 dir := dirs.NewWindmillDirAt(f.Path()) 1026 docker := docker.NewFakeClient() 1027 docker.ContainerListOutput = map[string][]types.Container{ 1028 "pod": []types.Container{ 1029 types.Container{ 1030 ID: k8s.MagicTestContainerID, 1031 }, 1032 }, 1033 } 1034 k8s := k8s.NewFakeK8sClient() 1035 k8s.Runtime = runtime 1036 sCli := synclet.NewTestSyncletClient(docker) 1037 mode := UpdateModeFlag(UpdateModeAuto) 1038 dcc := dockercompose.NewFakeDockerComposeClient(t, ctx) 1039 kp := &fakeKINDPusher{} 1040 bd, err := provideBuildAndDeployer(ctx, docker, k8s, dir, env, mode, sCli, dcc, fakeClock{now: time.Unix(1551202573, 0)}, kp, ta) 1041 if err != nil { 1042 t.Fatal(err) 1043 } 1044 1045 st, _ := store.NewStoreForTesting() 1046 1047 return &bdFixture{ 1048 TempDirFixture: f, 1049 ctx: ctx, 1050 cancel: cancel, 1051 docker: docker, 1052 k8s: k8s, 1053 sCli: sCli, 1054 bd: bd, 1055 st: st, 1056 dcCli: dcc, 1057 logs: logs, 1058 } 1059 } 1060 1061 func (f *bdFixture) TearDown() { 1062 f.k8s.TearDown() 1063 f.cancel() 1064 f.TempDirFixture.TearDown() 1065 } 1066 1067 func (f *bdFixture) NewPathSet(paths ...string) model.PathSet { 1068 return model.NewPathSet(paths, f.Path()) 1069 } 1070 1071 func (f *bdFixture) assertContainerRestarts(count int) { 1072 // Ensure that MagicTestContainerID was the only container id that saw 1073 // restarts, and that it saw the right number of restarts. 1074 expected := map[string]int{} 1075 if count != 0 { 1076 expected[string(k8s.MagicTestContainerID)] = count 1077 } 1078 assert.Equal(f.T(), expected, f.docker.RestartsByContainer, 1079 "checking for expected # of container restarts") 1080 } 1081 1082 // Total number of restarts, regardless of which container. 1083 func (f *bdFixture) assertTotalContainerRestarts(count int) { 1084 assert.Len(f.T(), f.docker.RestartsByContainer, count, 1085 "checking for expected # of container restarts") 1086 } 1087 1088 func (f *bdFixture) assertK8sUpsertCalled(called bool) { 1089 assert.Equal(f.T(), called, f.k8s.Yaml != "", 1090 "checking that k8s.Upsert was called") 1091 } 1092 1093 func (f *bdFixture) createBuildStateSet(manifest model.Manifest, changedFiles []string) store.BuildStateSet { 1094 bs := store.BuildStateSet{} 1095 1096 // If there are no changed files, the test wants a build state where 1097 // nothing has ever been built. 1098 // 1099 // If there are changed files, the test wants a build state where 1100 // everything has been built once. The changed files chould be 1101 // attached to the appropriate build state. 1102 if len(changedFiles) == 0 { 1103 return bs 1104 } 1105 1106 consumedFiles := make(map[string]bool) 1107 for _, iTarget := range manifest.ImageTargets { 1108 filesChangingImage := []string{} 1109 for _, file := range changedFiles { 1110 fullPath := f.JoinPath(file) 1111 inDeps := false 1112 for _, dep := range iTarget.Dependencies() { 1113 if ospath.IsChild(dep, fullPath) { 1114 inDeps = true 1115 break 1116 } 1117 } 1118 1119 if inDeps { 1120 filesChangingImage = append(filesChangingImage, f.WriteFile(file, "blah")) 1121 consumedFiles[file] = true 1122 } 1123 } 1124 1125 state := store.NewBuildState(alreadyBuilt, filesChangingImage) 1126 if manifest.IsImageDeployed(iTarget) { 1127 state = state.WithRunningContainers([]store.ContainerInfo{testContainerInfo}) 1128 } 1129 bs[iTarget.ID()] = state 1130 } 1131 1132 if len(consumedFiles) != len(changedFiles) { 1133 f.T().Fatalf("testCase has files that weren't consumed by an image. "+ 1134 "Was that intentional?\nChangedFiles: %v\nConsumedFiles: %v\n", 1135 changedFiles, consumedFiles) 1136 } 1137 return bs 1138 } 1139 1140 func resultToStateSet(resultSet store.BuildResultSet, files []string, cInfo store.ContainerInfo) store.BuildStateSet { 1141 stateSet := store.BuildStateSet{} 1142 for id, result := range resultSet { 1143 state := store.NewBuildState(result, files).WithRunningContainers([]store.ContainerInfo{cInfo}) 1144 stateSet[id] = state 1145 } 1146 return stateSet 1147 } 1148 1149 type fakeClock struct { 1150 now time.Time 1151 } 1152 1153 func (c fakeClock) Now() time.Time { return c.now }