github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/engine/buildcontrol/build_control_test.go (about) 1 package buildcontrol 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "testing" 7 "time" 8 9 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 11 "github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1" 12 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 v1 "k8s.io/api/core/v1" 16 17 "github.com/tilt-dev/tilt/internal/container" 18 "github.com/tilt-dev/tilt/internal/k8s" 19 "github.com/tilt-dev/tilt/internal/k8s/testyaml" 20 "github.com/tilt-dev/tilt/internal/store" 21 "github.com/tilt-dev/tilt/internal/store/k8sconv" 22 "github.com/tilt-dev/tilt/internal/testutils/manifestbuilder" 23 "github.com/tilt-dev/tilt/internal/testutils/tempdir" 24 "github.com/tilt-dev/tilt/pkg/model" 25 ) 26 27 func TestNextTargetToBuildDoesntReturnCurrentlyBuildingTarget(t *testing.T) { 28 f := newTestFixture(t) 29 30 mt := f.upsertK8sManifest("k8s1") 31 f.st.UpsertManifestTarget(mt) 32 33 // Verify this target is normally next-to-build 34 f.assertNextTargetToBuild(mt.Manifest.Name) 35 36 // If target is currently building, should NOT be next-to-build 37 mt.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 38 f.assertNoTargetNextToBuild() 39 } 40 41 func TestCurrentlyBuildingK8sResourceDisablesLocalScheduling(t *testing.T) { 42 f := newTestFixture(t) 43 44 k8s1 := f.upsertK8sManifest("k8s1") 45 k8s2 := f.upsertK8sManifest("k8s2") 46 f.upsertLocalManifest("local1") 47 48 f.assertNextTargetToBuild("local1") 49 50 k8s1.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 51 f.assertNextTargetToBuild("k8s2") 52 f.assertHold("local1", store.HoldReasonIsUnparallelizableTarget) 53 54 k8s2.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 55 f.assertNoTargetNextToBuild() 56 } 57 58 func TestCurrentlyBuildingK8sResourceDoesNotCreateHoldIfResourceNotPending(t *testing.T) { 59 f := newTestFixture(t) 60 61 k8s1 := f.upsertK8sManifest("k8s1") 62 k8s2 := f.upsertK8sManifest("k8s2") 63 f.upsertLocalManifest("local1", func(m manifestbuilder.ManifestBuilder) manifestbuilder.ManifestBuilder { 64 return m.WithTriggerMode(model.TriggerModeManual) 65 }) 66 67 f.assertHold("local1", store.HoldReasonNone) 68 69 k8s1.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 70 f.assertNextTargetToBuild("k8s2") 71 f.assertHold("local1", store.HoldReasonNone) 72 73 k8s2.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 74 f.assertNoTargetNextToBuild() 75 f.assertHold("local1", store.HoldReasonNone) 76 } 77 78 func TestCurrentlyBuildingUncategorizedDisablesOtherK8sTargets(t *testing.T) { 79 f := newTestFixture(t) 80 81 _ = f.upsertK8sManifest("k8s1") 82 k8sUnresourced := f.upsertK8sManifest(model.UnresourcedYAMLManifestName) 83 _ = f.upsertK8sManifest("k8s2") 84 85 f.assertNextTargetToBuild(model.UnresourcedYAMLManifestName) 86 k8sUnresourced.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 87 f.assertNoTargetNextToBuild() 88 for _, mn := range []model.ManifestName{"k8s1", "k8s2"} { 89 f.assertHold(mn, store.HoldReasonWaitingForUncategorized, model.ManifestName("uncategorized").TargetID()) 90 } 91 } 92 93 func TestK8sDependsOnLocal(t *testing.T) { 94 f := newTestFixture(t) 95 96 k8s1 := f.upsertK8sManifest("k8s1", withResourceDeps("local1")) 97 k8s2 := f.upsertK8sManifest("k8s2") 98 local1 := f.upsertLocalManifest("local1") 99 100 f.assertNextTargetToBuild("local1") 101 102 f.assertHold("k8s1", store.HoldReasonWaitingForDep, model.ManifestName("local1").TargetID()) 103 f.assertHold("k8s2", store.HoldReasonNone) 104 105 local1.State.AddCompletedBuild(model.BuildRecord{ 106 StartTime: time.Now(), 107 FinishTime: time.Now(), 108 }) 109 lrs := local1.State.LocalRuntimeState() 110 lrs.LastReadyOrSucceededTime = time.Now() 111 local1.State.RuntimeState = lrs 112 113 f.assertNextTargetToBuild("k8s1") 114 k8s1.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 115 f.assertNextTargetToBuild("k8s2") 116 117 _ = k8s2 118 } 119 120 func TestLocalDependsOnNonWorkloadK8s(t *testing.T) { 121 f := newTestFixture(t) 122 123 local1 := f.upsertLocalManifest("local1", withResourceDeps("k8s1")) 124 k8s1 := f.upsertK8sManifest("k8s1", withK8sPodReadiness(model.PodReadinessIgnore)) 125 k8s2 := f.upsertK8sManifest("k8s2", withK8sPodReadiness(model.PodReadinessIgnore)) 126 127 f.assertNextTargetToBuild("k8s1") 128 f.assertHold("local1", store.HoldReasonWaitingForDep, model.ManifestName("k8s1").TargetID()) 129 f.assertHold("k8s1", store.HoldReasonNone) 130 f.assertHold("k8s2", store.HoldReasonNone) 131 132 k8s1.State.AddCompletedBuild(model.BuildRecord{ 133 StartTime: time.Now(), 134 FinishTime: time.Now(), 135 }) 136 k8s1.State.RuntimeState = store.K8sRuntimeState{ 137 PodReadinessMode: model.PodReadinessIgnore, 138 HasEverDeployedSuccessfully: true, 139 } 140 141 f.assertNextTargetToBuild("local1") 142 local1.State.AddCompletedBuild(model.BuildRecord{ 143 StartTime: time.Now(), 144 FinishTime: time.Now(), 145 }) 146 f.assertNextTargetToBuild("k8s2") 147 148 _ = k8s2 149 } 150 151 func TestK8sDependsOnCluster(t *testing.T) { 152 f := newTestFixture(t) 153 154 f.st.Clusters["default"].Status.Error = "connection error" 155 156 _ = f.upsertK8sManifest("k8s1") 157 f.assertNoTargetNextToBuild() 158 f.assertHoldOnRefs("k8s1", store.HoldReasonCluster, v1alpha1.UIResourceStateWaitingOnRef{ 159 Group: "tilt.dev", 160 APIVersion: "v1alpha1", 161 Kind: "Cluster", 162 Name: "default", 163 }) 164 165 f.st.Clusters["default"].Status.Error = "" 166 f.assertNextTargetToBuild("k8s1") 167 } 168 169 func TestCurrentlyBuildingLocalResourceDisablesK8sScheduling(t *testing.T) { 170 f := newTestFixture(t) 171 172 f.upsertK8sManifest("k8s1") 173 f.upsertK8sManifest("k8s2") 174 local1 := f.upsertLocalManifest("local1") 175 f.upsertLocalManifest("local2") 176 177 f.assertNextTargetToBuild("local1") 178 local1.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 179 f.assertNoTargetNextToBuild() 180 for _, mn := range []model.ManifestName{"k8s1", "k8s2", "local2"} { 181 f.assertHold(mn, store.HoldReasonWaitingForUnparallelizableTarget, model.ManifestName("local1").TargetID()) 182 } 183 } 184 185 func TestCurrentlyBuildingParallelLocalResource(t *testing.T) { 186 f := newTestFixture(t) 187 188 f.upsertK8sManifest("k8s1") 189 local1 := f.upsertLocalManifest("local1", func(m manifestbuilder.ManifestBuilder) manifestbuilder.ManifestBuilder { 190 return m.WithLocalAllowParallel(true) 191 }) 192 local2 := f.upsertLocalManifest("local2", func(m manifestbuilder.ManifestBuilder) manifestbuilder.ManifestBuilder { 193 return m.WithLocalAllowParallel(true) 194 }) 195 196 f.assertNextTargetToBuild("local1") 197 198 local1.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 199 f.assertNextTargetToBuild("local2") 200 201 local2.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 202 f.assertNextTargetToBuild("k8s1") 203 } 204 205 func TestTriggerIneligibleResource(t *testing.T) { 206 f := newTestFixture(t) 207 208 // local1 has a build in progress 209 local1 := f.upsertLocalManifest("local1", func(m manifestbuilder.ManifestBuilder) manifestbuilder.ManifestBuilder { 210 return m.WithLocalAllowParallel(true) 211 }) 212 local1.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 213 214 // local2 is not parallelizable 215 local2 := f.upsertLocalManifest("local2") 216 217 f.st.AppendToTriggerQueue(local1.Manifest.Name, model.BuildReasonFlagTriggerCLI) 218 f.st.AppendToTriggerQueue(local2.Manifest.Name, model.BuildReasonFlagTriggerCLI) 219 f.assertNoTargetNextToBuild() 220 } 221 222 func TestTwoK8sTargetsWithBaseImage(t *testing.T) { 223 f := newTestFixture(t) 224 225 baseImage := newDockerImageTarget("sancho-base") 226 sanchoOneImage := newDockerImageTarget("sancho-one"). 227 WithImageMapDeps([]string{baseImage.ImageMapName()}) 228 sanchoTwoImage := newDockerImageTarget("sancho-two"). 229 WithImageMapDeps([]string{baseImage.ImageMapName()}) 230 231 sanchoOne := f.upsertManifest(manifestbuilder.New(f, "sancho-one"). 232 WithImageTargets(baseImage, sanchoOneImage). 233 WithK8sYAML(testyaml.SanchoYAML). 234 Build()) 235 f.upsertManifest(manifestbuilder.New(f, "sancho-two"). 236 WithImageTargets(baseImage, sanchoTwoImage). 237 WithK8sYAML(testyaml.SanchoYAML). 238 Build()) 239 240 f.assertNextTargetToBuild("sancho-one") 241 242 sanchoOne.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 243 244 f.assertNoTargetNextToBuild() 245 f.assertHold("sancho-two", store.HoldReasonBuildingComponent, baseImage.ID()) 246 247 delete(sanchoOne.State.CurrentBuilds, "buildcontrol") 248 sanchoOne.State.AddCompletedBuild(model.BuildRecord{ 249 StartTime: time.Now(), 250 FinishTime: time.Now(), 251 }) 252 253 f.assertNextTargetToBuild("sancho-two") 254 } 255 256 func TestLiveUpdateMainImageHold(t *testing.T) { 257 f := newTestFixture(t) 258 259 srcFile := f.JoinPath("src", "a.txt") 260 f.WriteFile(srcFile, "hello") 261 luSpec := v1alpha1.LiveUpdateSpec{ 262 BasePath: f.Path(), 263 Syncs: []v1alpha1.LiveUpdateSync{ 264 {LocalPath: "src", ContainerPath: "/src"}, 265 }, 266 Sources: []v1alpha1.LiveUpdateSource{ 267 {FileWatch: "image:sancho"}, 268 }, 269 Selector: v1alpha1.LiveUpdateSelector{ 270 Kubernetes: &v1alpha1.LiveUpdateKubernetesSelector{ 271 ContainerName: "c", 272 }, 273 }, 274 } 275 f.st.LiveUpdates["sancho"] = &v1alpha1.LiveUpdate{Spec: luSpec} 276 277 baseImage := newDockerImageTarget("sancho-base") 278 sanchoImage := newDockerImageTarget("sancho"). 279 WithLiveUpdateSpec("sancho", luSpec). 280 WithImageMapDeps([]string{baseImage.ImageMapName()}) 281 282 sancho := f.upsertManifest(manifestbuilder.New(f, "sancho"). 283 WithImageTargets(baseImage, sanchoImage). 284 WithK8sYAML(testyaml.SanchoYAML). 285 Build()) 286 287 f.assertNextTargetToBuild("sancho") 288 sancho.State.AddCompletedBuild(model.BuildRecord{ 289 StartTime: time.Now(), 290 FinishTime: time.Now(), 291 }) 292 293 resource := &k8sconv.KubernetesResource{ 294 FilteredPods: []v1alpha1.Pod{ 295 *readyPod("pod-1", sanchoImage.ImageMapSpec.Selector), 296 }, 297 } 298 f.st.KubernetesResources["sancho"] = resource 299 300 sancho.State.MutableBuildStatus(sanchoImage.ID()).PendingFileChanges[srcFile] = time.Now() 301 f.assertNoTargetNextToBuild() 302 f.assertHold("sancho", store.HoldReasonReconciling) 303 304 // If the live update is failing, we have to rebuild. 305 f.st.LiveUpdates["sancho"] = &v1alpha1.LiveUpdate{ 306 Spec: luSpec, 307 Status: v1alpha1.LiveUpdateStatus{Failed: &v1alpha1.LiveUpdateStateFailed{Reason: "fake-reason"}}, 308 } 309 f.assertNextTargetToBuild("sancho") 310 311 // reset to a good state. 312 f.st.LiveUpdates["sancho"] = &v1alpha1.LiveUpdate{Spec: luSpec} 313 f.assertNoTargetNextToBuild() 314 315 // If the base image has a change, we have to rebuild. 316 sancho.State.MutableBuildStatus(baseImage.ID()).PendingFileChanges[srcFile] = time.Now() 317 f.assertNextTargetToBuild("sancho") 318 } 319 320 // Test to make sure the buildcontroller does the translation 321 // correctly between the image target with the file watch 322 // and the image target matching the deployed container. 323 func TestLiveUpdateBaseImageHold(t *testing.T) { 324 f := newTestFixture(t) 325 326 srcFile := f.JoinPath("base", "a.txt") 327 f.WriteFile(srcFile, "hello") 328 329 luSpec := v1alpha1.LiveUpdateSpec{ 330 BasePath: f.Path(), 331 Syncs: []v1alpha1.LiveUpdateSync{ 332 {LocalPath: "base", ContainerPath: "/base"}, 333 }, 334 Sources: []v1alpha1.LiveUpdateSource{ 335 {FileWatch: "image:sancho-base"}, 336 }, 337 } 338 f.st.LiveUpdates["sancho"] = &v1alpha1.LiveUpdate{Spec: luSpec} 339 340 baseImage := newDockerImageTarget("sancho-base") 341 sanchoImage := newDockerImageTarget("sancho"). 342 WithLiveUpdateSpec("sancho", luSpec). 343 WithImageMapDeps([]string{baseImage.ImageMapName()}) 344 345 sancho := f.upsertManifest(manifestbuilder.New(f, "sancho"). 346 WithImageTargets(baseImage, sanchoImage). 347 WithK8sYAML(testyaml.SanchoYAML). 348 Build()) 349 350 f.assertNextTargetToBuild("sancho") 351 sancho.State.AddCompletedBuild(model.BuildRecord{ 352 StartTime: time.Now(), 353 FinishTime: time.Now(), 354 }) 355 356 resource := &k8sconv.KubernetesResource{ 357 FilteredPods: []v1alpha1.Pod{ 358 *readyPod("pod-1", sanchoImage.Selector), 359 }, 360 } 361 f.st.KubernetesResources["sancho"] = resource 362 363 sancho.State.MutableBuildStatus(baseImage.ID()).PendingFileChanges[srcFile] = time.Now() 364 f.assertNoTargetNextToBuild() 365 f.assertHold("sancho", store.HoldReasonReconciling) 366 367 // If the live update is failing, we have to rebuild. 368 f.st.LiveUpdates["sancho"] = &v1alpha1.LiveUpdate{ 369 Spec: luSpec, 370 Status: v1alpha1.LiveUpdateStatus{Failed: &v1alpha1.LiveUpdateStateFailed{Reason: "fake-reason"}}, 371 } 372 f.assertNextTargetToBuild("sancho") 373 374 // reset to a good state. 375 f.st.LiveUpdates["sancho"] = &v1alpha1.LiveUpdate{Spec: luSpec} 376 f.assertNoTargetNextToBuild() 377 378 // If the deploy image has a change, we have to rebuild. 379 sancho.State.MutableBuildStatus(sanchoImage.ID()).PendingFileChanges[srcFile] = time.Now() 380 f.assertNextTargetToBuild("sancho") 381 } 382 383 func TestTwoK8sTargetsWithBaseImagePrebuilt(t *testing.T) { 384 f := newTestFixture(t) 385 386 baseImage := newDockerImageTarget("sancho-base") 387 sanchoOneImage := newDockerImageTarget("sancho-one"). 388 WithImageMapDeps([]string{baseImage.ImageMapName()}) 389 sanchoTwoImage := newDockerImageTarget("sancho-two"). 390 WithImageMapDeps([]string{baseImage.ImageMapName()}) 391 392 sanchoOne := f.upsertManifest(manifestbuilder.New(f, "sancho-one"). 393 WithImageTargets(baseImage, sanchoOneImage). 394 WithK8sYAML(testyaml.SanchoYAML). 395 Build()) 396 sanchoTwo := f.upsertManifest(manifestbuilder.New(f, "sancho-two"). 397 WithImageTargets(baseImage, sanchoTwoImage). 398 WithK8sYAML(testyaml.SanchoYAML). 399 Build()) 400 401 sanchoOne.State.MutableBuildStatus(baseImage.ID()).LastResult = store.ImageBuildResult{} 402 sanchoTwo.State.MutableBuildStatus(baseImage.ID()).LastResult = store.ImageBuildResult{} 403 404 f.assertNextTargetToBuild("sancho-one") 405 406 sanchoOne.State.CurrentBuilds["buildcontrol"] = model.BuildRecord{StartTime: time.Now()} 407 408 // Make sure sancho-two can start while sanchoOne is still pending. 409 f.assertNextTargetToBuild("sancho-two") 410 } 411 412 func TestHoldForDeploy(t *testing.T) { 413 f := newTestFixture(t) 414 415 srcFile := f.JoinPath("src", "a.txt") 416 objFile := f.JoinPath("obj", "a.out") 417 fallbackFile := f.JoinPath("src", "package.json") 418 f.WriteFile(srcFile, "hello") 419 f.WriteFile(objFile, "hello") 420 f.WriteFile(fallbackFile, "hello") 421 422 luSpec := v1alpha1.LiveUpdateSpec{ 423 BasePath: f.Path(), 424 StopPaths: []string{filepath.Join("src", "package.json")}, 425 Syncs: []v1alpha1.LiveUpdateSync{{LocalPath: "src", ContainerPath: "/src"}}, 426 Selector: v1alpha1.LiveUpdateSelector{ 427 Kubernetes: &v1alpha1.LiveUpdateKubernetesSelector{ 428 ContainerName: "c", 429 }, 430 }, 431 } 432 sanchoImage := newDockerImageTarget("sancho"). 433 WithLiveUpdateSpec("sancho", luSpec). 434 WithDockerImage(v1alpha1.DockerImageSpec{Context: f.Path()}) 435 sancho := f.upsertManifest(manifestbuilder.New(f, "sancho"). 436 WithImageTargets(sanchoImage). 437 WithK8sYAML(testyaml.SanchoYAML). 438 Build()) 439 440 f.assertNextTargetToBuild("sancho") 441 442 sancho.State.AddCompletedBuild(model.BuildRecord{ 443 StartTime: time.Now(), 444 FinishTime: time.Now(), 445 }) 446 f.assertNoTargetNextToBuild() 447 448 status := sancho.State.MutableBuildStatus(sanchoImage.ID()) 449 450 status.PendingFileChanges[objFile] = time.Now() 451 f.assertNextTargetToBuild("sancho") 452 delete(status.PendingFileChanges, objFile) 453 454 status.PendingFileChanges[fallbackFile] = time.Now() 455 f.assertNextTargetToBuild("sancho") 456 delete(status.PendingFileChanges, fallbackFile) 457 458 status.PendingFileChanges[srcFile] = time.Now() 459 f.assertNoTargetNextToBuild() 460 f.assertHold("sancho", store.HoldReasonWaitingForDeploy) 461 462 resource := &k8sconv.KubernetesResource{ 463 FilteredPods: []v1alpha1.Pod{}, 464 } 465 f.st.KubernetesResources["sancho"] = resource 466 467 resource.FilteredPods = append(resource.FilteredPods, *readyPod("pod-1", sanchoImage.Selector)) 468 f.assertNextTargetToBuild("sancho") 469 470 resource.FilteredPods[0] = *crashingPod("pod-1", sanchoImage.Selector) 471 f.assertNextTargetToBuild("sancho") 472 473 resource.FilteredPods[0] = *crashedInThePastPod("pod-1", sanchoImage.Selector) 474 f.assertNextTargetToBuild("sancho") 475 476 resource.FilteredPods[0] = *sidecarCrashedPod("pod-1", sanchoImage.Selector) 477 f.assertNextTargetToBuild("sancho") 478 479 resource.FilteredPods[0] = *completedPod("pod-1", sanchoImage.Selector) 480 f.assertNextTargetToBuild("sancho") 481 } 482 483 func TestHoldForManualLiveUpdate(t *testing.T) { 484 f := newTestFixture(t) 485 486 srcFile := f.JoinPath("src", "a.txt") 487 f.WriteFile(srcFile, "hello") 488 489 luSpec := v1alpha1.LiveUpdateSpec{ 490 BasePath: f.Path(), 491 Syncs: []v1alpha1.LiveUpdateSync{{LocalPath: "src", ContainerPath: "/src"}}, 492 Sources: []v1alpha1.LiveUpdateSource{ 493 {FileWatch: "image:sancho"}, 494 }, 495 } 496 sanchoImage := newDockerImageTarget("sancho"). 497 WithLiveUpdateSpec("sancho", luSpec). 498 WithDockerImage(v1alpha1.DockerImageSpec{Context: f.Path()}) 499 sancho := f.upsertManifest(manifestbuilder.New(f, "sancho"). 500 WithImageTargets(sanchoImage). 501 WithK8sYAML(testyaml.SanchoYAML). 502 WithTriggerMode(model.TriggerModeManualWithAutoInit). 503 Build()) 504 505 f.assertNextTargetToBuild("sancho") 506 507 // Set the live-update state to healthy. 508 sancho.State.AddCompletedBuild(model.BuildRecord{ 509 StartTime: time.Now(), 510 FinishTime: time.Now(), 511 }) 512 resource := &k8sconv.KubernetesResource{ 513 FilteredPods: []v1alpha1.Pod{*completedPod("pod-1", sanchoImage.Selector)}, 514 } 515 f.st.KubernetesResources["sancho"] = resource 516 f.st.LiveUpdates["sancho"] = &v1alpha1.LiveUpdate{Spec: luSpec} 517 f.assertNoTargetNextToBuild() 518 519 // This shouldn't trigger a full-build, because it will be handled by the live-updater. 520 status := sancho.State.MutableBuildStatus(sanchoImage.ID()) 521 f.st.AppendToTriggerQueue(sancho.Manifest.Name, model.BuildReasonFlagTriggerCLI) 522 status.PendingFileChanges[srcFile] = time.Now() 523 f.assertNoTargetNextToBuild() 524 525 // This should trigger a full-rebuild, because we have a trigger without pending changes. 526 delete(status.PendingFileChanges, srcFile) 527 f.assertNextTargetToBuild("sancho") 528 } 529 530 func TestHoldDisabled(t *testing.T) { 531 f := newTestFixture(t) 532 533 f.upsertLocalManifest("local") 534 f.st.ManifestTargets["local"].State.DisableState = v1alpha1.DisableStateDisabled 535 f.assertNoTargetNextToBuild() 536 } 537 538 func TestHoldIfAnyDisableStatusPending(t *testing.T) { 539 f := newTestFixture(t) 540 541 f.upsertLocalManifest("local1") 542 f.upsertLocalManifest("local2") 543 f.upsertLocalManifest("local3") 544 f.st.ManifestTargets["local2"].State.DisableState = v1alpha1.DisableStatePending 545 546 f.assertHold("local1", store.HoldReasonTiltfileReload, model.TargetID{Type: "manifest", Name: "local2"}) 547 f.assertHold("local2", store.HoldReasonTiltfileReload, model.TargetID{Type: "manifest", Name: "local2"}) 548 f.assertHold("local3", store.HoldReasonTiltfileReload, model.TargetID{Type: "manifest", Name: "local2"}) 549 f.assertNoTargetNextToBuild() 550 } 551 552 func readyPod(podID k8s.PodID, ref string) *v1alpha1.Pod { 553 return &v1alpha1.Pod{ 554 Name: podID.String(), 555 Phase: string(v1.PodRunning), 556 Status: "Running", 557 Containers: []v1alpha1.Container{ 558 { 559 ID: string(podID + "-container"), 560 Name: "c", 561 Ready: true, 562 Image: ref, 563 State: v1alpha1.ContainerState{ 564 Running: &v1alpha1.ContainerStateRunning{StartedAt: metav1.Now()}, 565 }, 566 }, 567 }, 568 } 569 } 570 571 func crashingPod(podID k8s.PodID, ref string) *v1alpha1.Pod { 572 return &v1alpha1.Pod{ 573 Name: podID.String(), 574 Phase: string(v1.PodRunning), 575 Status: "CrashLoopBackOff", 576 Containers: []v1alpha1.Container{ 577 { 578 ID: string(podID + "-container"), 579 Name: "c", 580 Ready: false, 581 Image: ref, 582 Restarts: 1, 583 State: v1alpha1.ContainerState{ 584 Terminated: &v1alpha1.ContainerStateTerminated{ 585 StartedAt: metav1.Now(), 586 FinishedAt: metav1.Now(), 587 Reason: "Error", 588 ExitCode: 127, 589 }}, 590 }, 591 }, 592 } 593 } 594 595 func crashedInThePastPod(podID k8s.PodID, ref string) *v1alpha1.Pod { 596 return &v1alpha1.Pod{ 597 Name: podID.String(), 598 Phase: string(v1.PodRunning), 599 Status: "Ready", 600 Containers: []v1alpha1.Container{ 601 { 602 ID: string(podID + "-container"), 603 Name: "c", 604 Ready: true, 605 Image: ref, 606 Restarts: 1, 607 State: v1alpha1.ContainerState{ 608 Running: &v1alpha1.ContainerStateRunning{StartedAt: metav1.Now()}, 609 }, 610 }, 611 }, 612 } 613 } 614 615 func sidecarCrashedPod(podID k8s.PodID, ref string) *v1alpha1.Pod { 616 return &v1alpha1.Pod{ 617 Name: podID.String(), 618 Phase: string(v1.PodRunning), 619 Status: "Ready", 620 Containers: []v1alpha1.Container{ 621 { 622 ID: string(podID + "-container"), 623 Name: "c", 624 Ready: true, 625 Image: ref, 626 Restarts: 0, 627 State: v1alpha1.ContainerState{ 628 Running: &v1alpha1.ContainerStateRunning{StartedAt: metav1.Now()}, 629 }, 630 }, 631 { 632 ID: string(podID + "-sidecar"), 633 Name: "s", 634 Ready: false, 635 Image: container.MustParseNamed("sidecar").String(), 636 Restarts: 1, 637 State: v1alpha1.ContainerState{ 638 Terminated: &v1alpha1.ContainerStateTerminated{ 639 StartedAt: metav1.Now(), 640 FinishedAt: metav1.Now(), 641 Reason: "Error", 642 ExitCode: 127, 643 }}, 644 }, 645 }, 646 } 647 } 648 649 func completedPod(podID k8s.PodID, ref string) *v1alpha1.Pod { 650 return &v1alpha1.Pod{ 651 Name: podID.String(), 652 Phase: string(v1.PodSucceeded), 653 Status: "Completed", 654 Containers: []v1alpha1.Container{ 655 { 656 ID: string(podID + "-container"), 657 Name: "c", 658 Ready: false, 659 Image: ref, 660 Restarts: 0, 661 State: v1alpha1.ContainerState{ 662 Terminated: &v1alpha1.ContainerStateTerminated{ 663 StartedAt: metav1.Now(), 664 FinishedAt: metav1.Now(), 665 Reason: "Success!", 666 ExitCode: 0, 667 }}, 668 }, 669 }, 670 } 671 } 672 673 type testFixture struct { 674 *tempdir.TempDirFixture 675 t *testing.T 676 st *store.EngineState 677 } 678 679 func newTestFixture(t *testing.T) testFixture { 680 f := tempdir.NewTempDirFixture(t) 681 st := store.NewState() 682 st.Clusters["default"] = &v1alpha1.Cluster{ 683 Status: v1alpha1.ClusterStatus{ 684 Arch: "amd64", 685 }, 686 } 687 return testFixture{ 688 TempDirFixture: f, 689 t: t, 690 st: st, 691 } 692 } 693 694 func (f *testFixture) assertHold(m model.ManifestName, reason store.HoldReason, holdOn ...model.TargetID) { 695 f.T().Helper() 696 _, hs := NextTargetToBuild(*f.st) 697 hold := store.Hold{ 698 Reason: reason, 699 HoldOn: holdOn, 700 } 701 assert.Equal(f.t, hold, hs[m]) 702 } 703 704 func (f *testFixture) assertHoldOnRefs(m model.ManifestName, reason store.HoldReason, onRefs ...v1alpha1.UIResourceStateWaitingOnRef) { 705 f.T().Helper() 706 _, hs := NextTargetToBuild(*f.st) 707 hold := store.Hold{ 708 Reason: reason, 709 OnRefs: onRefs, 710 } 711 assert.Equal(f.t, hold, hs[m]) 712 } 713 714 func (f *testFixture) assertNextTargetToBuild(expected model.ManifestName) { 715 f.T().Helper() 716 next, holds := NextTargetToBuild(*f.st) 717 require.NotNil(f.t, next, "expected next target %s but got: nil. holds: %v", expected, holds) 718 actual := next.Manifest.Name 719 assert.Equal(f.t, expected, actual, "expected next target to be %s but got %s", expected, actual) 720 } 721 722 func (f *testFixture) assertNoTargetNextToBuild() { 723 f.T().Helper() 724 next, _ := NextTargetToBuild(*f.st) 725 if next != nil { 726 f.t.Fatalf("expected no next target to build, but got %s", next.Manifest.Name) 727 } 728 } 729 730 func (f *testFixture) upsertManifest(m model.Manifest) *store.ManifestTarget { 731 mt := store.NewManifestTarget(m) 732 mt.State.DisableState = v1alpha1.DisableStateEnabled 733 f.st.UpsertManifestTarget(mt) 734 return mt 735 } 736 737 func (f *testFixture) upsertK8sManifest(name model.ManifestName, opts ...manifestOption) *store.ManifestTarget { 738 b := manifestbuilder.New(f, name) 739 for _, o := range opts { 740 b = o(b) 741 } 742 return f.upsertManifest(b.WithK8sYAML(testyaml.SanchoYAML).Build()) 743 } 744 745 func (f *testFixture) upsertLocalManifest(name model.ManifestName, opts ...manifestOption) *store.ManifestTarget { 746 b := manifestbuilder.New(f, name) 747 for _, o := range opts { 748 b = o(b) 749 } 750 return f.upsertManifest(b.WithLocalResource(fmt.Sprintf("exec-%s", name), nil).Build()) 751 } 752 753 type manifestOption func(manifestbuilder.ManifestBuilder) manifestbuilder.ManifestBuilder 754 755 func withResourceDeps(deps ...string) manifestOption { 756 return manifestOption(func(m manifestbuilder.ManifestBuilder) manifestbuilder.ManifestBuilder { 757 return m.WithResourceDeps(deps...) 758 }) 759 } 760 func withK8sPodReadiness(pr model.PodReadinessMode) manifestOption { 761 return manifestOption(func(m manifestbuilder.ManifestBuilder) manifestbuilder.ManifestBuilder { 762 return m.WithK8sPodReadiness(pr) 763 }) 764 }