github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/build/controller_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "errors" 21 "fmt" 22 "reflect" 23 "testing" 24 "time" 25 26 buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1" 27 duckv1alpha1 "github.com/knative/pkg/apis/duck/v1alpha1" 28 corev1 "k8s.io/api/core/v1" 29 "k8s.io/apimachinery/pkg/api/equality" 30 apierrors "k8s.io/apimachinery/pkg/api/errors" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/util/diff" 33 34 prowjobv1 "k8s.io/test-infra/prow/apis/prowjobs/v1" 35 "k8s.io/test-infra/prow/kube" 36 "k8s.io/test-infra/prow/pod-utils/decorate" 37 ) 38 39 const ( 40 errorGetProwJob = "error-get-prowjob" 41 errorGetBuild = "error-get-build" 42 errorDeleteBuild = "error-delete-build" 43 errorCreateBuild = "error-create-build" 44 errorUpdateProwJob = "error-update-prowjob" 45 ) 46 47 type fakeReconciler struct { 48 jobs map[string]prowjobv1.ProwJob 49 builds map[string]buildv1alpha1.Build 50 nows metav1.Time 51 } 52 53 func (r *fakeReconciler) now() metav1.Time { 54 fmt.Println(r.nows) 55 return r.nows 56 } 57 58 const fakePJCtx = "prow-context" 59 const fakePJNS = "prow-job" 60 61 func (r *fakeReconciler) getProwJob(name string) (*prowjobv1.ProwJob, error) { 62 if name == errorGetProwJob { 63 return nil, errors.New("injected get prowjob error") 64 } 65 k := toKey(fakePJCtx, fakePJNS, name) 66 pj, present := r.jobs[k] 67 if !present { 68 return nil, apierrors.NewNotFound(prowjobv1.Resource("ProwJob"), name) 69 } 70 return &pj, nil 71 } 72 73 func (r *fakeReconciler) updateProwJob(pj *prowjobv1.ProwJob) (*prowjobv1.ProwJob, error) { 74 if pj.Name == errorUpdateProwJob { 75 return nil, errors.New("injected update prowjob error") 76 } 77 if pj == nil { 78 return nil, errors.New("nil prowjob") 79 } 80 k := toKey(fakePJCtx, fakePJNS, pj.Name) 81 if _, present := r.jobs[k]; !present { 82 return nil, apierrors.NewNotFound(prowjobv1.Resource("ProwJob"), pj.Name) 83 } 84 r.jobs[k] = *pj 85 return pj, nil 86 } 87 88 func (r *fakeReconciler) getBuild(context, namespace, name string) (*buildv1alpha1.Build, error) { 89 if namespace == errorGetBuild { 90 return nil, errors.New("injected create build error") 91 } 92 k := toKey(context, namespace, name) 93 b, present := r.builds[k] 94 if !present { 95 return nil, apierrors.NewNotFound(buildv1alpha1.Resource("Build"), name) 96 } 97 return &b, nil 98 } 99 func (r *fakeReconciler) deleteBuild(context, namespace, name string) error { 100 if namespace == errorDeleteBuild { 101 return errors.New("injected create build error") 102 } 103 k := toKey(context, namespace, name) 104 if _, present := r.builds[k]; !present { 105 return apierrors.NewNotFound(buildv1alpha1.Resource("Build"), name) 106 } 107 delete(r.builds, k) 108 return nil 109 } 110 111 func (r *fakeReconciler) createBuild(context, namespace string, b *buildv1alpha1.Build) (*buildv1alpha1.Build, error) { 112 if b == nil { 113 return nil, errors.New("nil build") 114 } 115 if namespace == errorCreateBuild { 116 return nil, errors.New("injected create build error") 117 } 118 k := toKey(context, namespace, b.Name) 119 if _, alreadyExists := r.builds[k]; alreadyExists { 120 return nil, apierrors.NewAlreadyExists(prowjobv1.Resource("ProwJob"), b.Name) 121 } 122 r.builds[k] = *b 123 return b, nil 124 } 125 126 func (r *fakeReconciler) buildID(pj prowjobv1.ProwJob) (string, error) { 127 return "7777777777", nil 128 } 129 130 type fakeLimiter struct { 131 added string 132 } 133 134 func (fl *fakeLimiter) ShutDown() {} 135 func (fl *fakeLimiter) Get() (interface{}, bool) { 136 return "not implemented", true 137 } 138 func (fl *fakeLimiter) Done(interface{}) {} 139 func (fl *fakeLimiter) Forget(interface{}) {} 140 func (fl *fakeLimiter) AddRateLimited(a interface{}) { 141 fl.added = a.(string) 142 } 143 144 func TestEnqueueKey(t *testing.T) { 145 cases := []struct { 146 name string 147 context string 148 obj interface{} 149 expected string 150 }{ 151 { 152 name: "enqueue build directly", 153 context: "hey", 154 obj: &buildv1alpha1.Build{ 155 ObjectMeta: metav1.ObjectMeta{ 156 Namespace: "foo", 157 Name: "bar", 158 }, 159 }, 160 expected: toKey("hey", "foo", "bar"), 161 }, 162 { 163 name: "enqueue prowjob's spec namespace", 164 context: "rolo", 165 obj: &prowjobv1.ProwJob{ 166 ObjectMeta: metav1.ObjectMeta{ 167 Namespace: "default", 168 Name: "dude", 169 }, 170 Spec: prowjobv1.ProwJobSpec{ 171 Namespace: "tomassi", 172 }, 173 }, 174 expected: toKey("rolo", "tomassi", "dude"), 175 }, 176 { 177 name: "ignore random object", 178 context: "foo", 179 obj: "bar", 180 }, 181 } 182 183 for _, tc := range cases { 184 t.Run(tc.name, func(t *testing.T) { 185 var fl fakeLimiter 186 c := controller{ 187 workqueue: &fl, 188 } 189 c.enqueueKey(tc.context, tc.obj) 190 if !reflect.DeepEqual(fl.added, tc.expected) { 191 t.Errorf("%q != expected %q", fl.added, tc.expected) 192 } 193 }) 194 } 195 } 196 197 func TestReconcile(t *testing.T) { 198 now := metav1.Now() 199 buildSpec := buildv1alpha1.BuildSpec{} 200 noJobChange := func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob { 201 return pj 202 } 203 noBuildChange := func(_ prowjobv1.ProwJob, b buildv1alpha1.Build) buildv1alpha1.Build { 204 return b 205 } 206 cases := []struct { 207 name string 208 namespace string 209 context string 210 observedJob *prowjobv1.ProwJob 211 observedBuild *buildv1alpha1.Build 212 expectedJob func(prowjobv1.ProwJob, buildv1alpha1.Build) prowjobv1.ProwJob 213 expectedBuild func(prowjobv1.ProwJob, buildv1alpha1.Build) buildv1alpha1.Build 214 err bool 215 }{{ 216 name: "new prow job creates build", 217 observedJob: &prowjobv1.ProwJob{ 218 Spec: prowjobv1.ProwJobSpec{ 219 Agent: prowjobv1.KnativeBuildAgent, 220 BuildSpec: &buildSpec, 221 }, 222 }, 223 expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob { 224 pj.Status = prowjobv1.ProwJobStatus{ 225 StartTime: now, 226 State: prowjobv1.TriggeredState, 227 Description: descScheduling, 228 } 229 return pj 230 }, 231 expectedBuild: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) buildv1alpha1.Build { 232 pj.Spec.Type = prowjobv1.PeriodicJob 233 b, err := makeBuild(pj, "50") 234 if err != nil { 235 panic(err) 236 } 237 return *b 238 }, 239 }, 240 { 241 name: "do not create build for failed prowjob", 242 observedJob: &prowjobv1.ProwJob{ 243 Spec: prowjobv1.ProwJobSpec{ 244 Agent: prowjobv1.KnativeBuildAgent, 245 BuildSpec: &buildSpec, 246 }, 247 Status: prowjobv1.ProwJobStatus{ 248 State: prowjobv1.FailureState, 249 }, 250 }, 251 expectedJob: noJobChange, 252 }, 253 { 254 name: "do not create build for successful prowjob", 255 observedJob: &prowjobv1.ProwJob{ 256 Spec: prowjobv1.ProwJobSpec{ 257 Agent: prowjobv1.KnativeBuildAgent, 258 BuildSpec: &buildSpec, 259 }, 260 Status: prowjobv1.ProwJobStatus{ 261 State: prowjobv1.SuccessState, 262 }, 263 }, 264 expectedJob: noJobChange, 265 }, 266 { 267 name: "do not create build for aborted prowjob", 268 observedJob: &prowjobv1.ProwJob{ 269 Spec: prowjobv1.ProwJobSpec{ 270 Agent: prowjobv1.KnativeBuildAgent, 271 BuildSpec: &buildSpec, 272 }, 273 Status: prowjobv1.ProwJobStatus{ 274 State: prowjobv1.AbortedState, 275 }, 276 }, 277 expectedJob: noJobChange, 278 }, 279 { 280 name: "delete build after deleting prowjob", 281 observedBuild: func() *buildv1alpha1.Build { 282 pj := prowjobv1.ProwJob{} 283 pj.Spec.Type = prowjobv1.PeriodicJob 284 pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{} 285 b, err := makeBuild(pj, "7") 286 if err != nil { 287 panic(err) 288 } 289 return b 290 }(), 291 }, 292 { 293 name: "do not delete deleted builds", 294 observedBuild: func() *buildv1alpha1.Build { 295 pj := prowjobv1.ProwJob{} 296 pj.Spec.Type = prowjobv1.PeriodicJob 297 pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{} 298 b, err := makeBuild(pj, "6") 299 b.DeletionTimestamp = &now 300 if err != nil { 301 panic(err) 302 } 303 return b 304 }(), 305 expectedBuild: noBuildChange, 306 }, 307 { 308 name: "only delete builds created by controller", 309 observedBuild: func() *buildv1alpha1.Build { 310 pj := prowjobv1.ProwJob{} 311 pj.Spec.Type = prowjobv1.PeriodicJob 312 pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{} 313 b, err := makeBuild(pj, "9999") 314 if err != nil { 315 panic(err) 316 } 317 delete(b.Labels, kube.CreatedByProw) 318 return b 319 }(), 320 expectedBuild: noBuildChange, 321 }, 322 { 323 name: "delete prow builds in the wrong cluster", 324 context: "wrong-cluster", 325 observedJob: &prowjobv1.ProwJob{ 326 Spec: prowjobv1.ProwJobSpec{ 327 Agent: prowjobv1.KnativeBuildAgent, 328 Cluster: "target-cluster", 329 BuildSpec: &buildv1alpha1.BuildSpec{ 330 ServiceAccountName: "robot", 331 }, 332 }, 333 Status: prowjobv1.ProwJobStatus{ 334 State: prowjobv1.PendingState, 335 StartTime: metav1.Now(), 336 Description: "fancy", 337 }, 338 }, 339 observedBuild: func() *buildv1alpha1.Build { 340 pj := prowjobv1.ProwJob{} 341 pj.Spec.Type = prowjobv1.PeriodicJob 342 pj.Spec.Agent = prowjobv1.KnativeBuildAgent 343 pj.Spec.BuildSpec = &buildSpec 344 b, err := makeBuild(pj, "5") 345 if err != nil { 346 panic(err) 347 } 348 return b 349 }(), 350 expectedJob: noJobChange, 351 }, 352 { 353 name: "ignore random builds in the wrong cluster", 354 context: "wrong-cluster", 355 observedJob: &prowjobv1.ProwJob{ 356 Spec: prowjobv1.ProwJobSpec{ 357 Agent: prowjobv1.KnativeBuildAgent, 358 Cluster: "target-cluster", 359 BuildSpec: &buildv1alpha1.BuildSpec{ 360 ServiceAccountName: "robot", 361 }, 362 }, 363 Status: prowjobv1.ProwJobStatus{ 364 State: prowjobv1.PendingState, 365 StartTime: metav1.Now(), 366 Description: "fancy", 367 }, 368 }, 369 observedBuild: func() *buildv1alpha1.Build { 370 pj := prowjobv1.ProwJob{} 371 pj.Spec.Type = prowjobv1.PeriodicJob 372 pj.Spec.Agent = prowjobv1.KnativeBuildAgent 373 pj.Spec.BuildSpec = &buildSpec 374 b, err := makeBuild(pj, "5") 375 if err != nil { 376 panic(err) 377 } 378 delete(b.Labels, kube.CreatedByProw) 379 return b 380 }(), 381 expectedJob: noJobChange, 382 expectedBuild: noBuildChange, 383 }, 384 { 385 name: "update job status if build resets", 386 observedJob: &prowjobv1.ProwJob{ 387 Spec: prowjobv1.ProwJobSpec{ 388 Agent: prowjobv1.KnativeBuildAgent, 389 BuildSpec: &buildv1alpha1.BuildSpec{ 390 ServiceAccountName: "robot", 391 }, 392 }, 393 Status: prowjobv1.ProwJobStatus{ 394 State: prowjobv1.PendingState, 395 StartTime: metav1.Now(), 396 Description: "fancy", 397 }, 398 }, 399 observedBuild: func() *buildv1alpha1.Build { 400 pj := prowjobv1.ProwJob{} 401 pj.Spec.Type = prowjobv1.PeriodicJob 402 pj.Spec.Agent = prowjobv1.KnativeBuildAgent 403 pj.Spec.BuildSpec = &buildSpec 404 b, err := makeBuild(pj, "5") 405 if err != nil { 406 panic(err) 407 } 408 return b 409 }(), 410 expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob { 411 pj.Status.State = prowjobv1.TriggeredState 412 pj.Status.Description = descScheduling 413 return pj 414 }, 415 expectedBuild: noBuildChange, 416 }, 417 { 418 name: "prowjob goes pending when build starts", 419 observedJob: &prowjobv1.ProwJob{ 420 Spec: prowjobv1.ProwJobSpec{ 421 Agent: prowjobv1.KnativeBuildAgent, 422 BuildSpec: &buildSpec, 423 }, 424 Status: prowjobv1.ProwJobStatus{ 425 State: prowjobv1.TriggeredState, 426 Description: "fancy", 427 }, 428 }, 429 observedBuild: func() *buildv1alpha1.Build { 430 pj := prowjobv1.ProwJob{} 431 pj.Spec.Type = prowjobv1.PeriodicJob 432 pj.Spec.Agent = prowjobv1.KnativeBuildAgent 433 pj.Spec.BuildSpec = &buildSpec 434 b, err := makeBuild(pj, "1") 435 if err != nil { 436 panic(err) 437 } 438 b.Status.SetCondition(&duckv1alpha1.Condition{ 439 Type: buildv1alpha1.BuildSucceeded, 440 Message: "hello", 441 }) 442 b.Status.StartTime = now 443 return b 444 }(), 445 expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob { 446 pj.Status = prowjobv1.ProwJobStatus{ 447 StartTime: now, 448 State: prowjobv1.PendingState, 449 Description: "hello", 450 } 451 return pj 452 }, 453 expectedBuild: noBuildChange, 454 }, 455 { 456 name: "prowjob succeeds when build succeeds", 457 observedJob: &prowjobv1.ProwJob{ 458 Spec: prowjobv1.ProwJobSpec{ 459 Agent: prowjobv1.KnativeBuildAgent, 460 BuildSpec: &buildSpec, 461 }, 462 Status: prowjobv1.ProwJobStatus{ 463 State: prowjobv1.PendingState, 464 Description: "fancy", 465 }, 466 }, 467 observedBuild: func() *buildv1alpha1.Build { 468 pj := prowjobv1.ProwJob{} 469 pj.Spec.Type = prowjobv1.PeriodicJob 470 pj.Spec.Agent = prowjobv1.KnativeBuildAgent 471 pj.Spec.BuildSpec = &buildSpec 472 b, err := makeBuild(pj, "22") 473 if err != nil { 474 panic(err) 475 } 476 b.Status.SetCondition(&duckv1alpha1.Condition{ 477 Type: buildv1alpha1.BuildSucceeded, 478 Status: corev1.ConditionTrue, 479 Message: "hello", 480 }) 481 b.Status.CompletionTime = now 482 b.Status.StartTime = now 483 return b 484 }(), 485 expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob { 486 pj.Status = prowjobv1.ProwJobStatus{ 487 StartTime: now, 488 CompletionTime: &now, 489 State: prowjobv1.SuccessState, 490 Description: "hello", 491 } 492 return pj 493 }, 494 expectedBuild: noBuildChange, 495 }, 496 { 497 name: "prowjob fails when build fails", 498 observedJob: &prowjobv1.ProwJob{ 499 Spec: prowjobv1.ProwJobSpec{ 500 Agent: prowjobv1.KnativeBuildAgent, 501 BuildSpec: &buildSpec, 502 }, 503 Status: prowjobv1.ProwJobStatus{ 504 State: prowjobv1.PendingState, 505 Description: "fancy", 506 }, 507 }, 508 observedBuild: func() *buildv1alpha1.Build { 509 pj := prowjobv1.ProwJob{} 510 pj.Spec.Type = prowjobv1.PeriodicJob 511 pj.Spec.Agent = prowjobv1.KnativeBuildAgent 512 pj.Spec.BuildSpec = &buildSpec 513 b, err := makeBuild(pj, "21") 514 if err != nil { 515 panic(err) 516 } 517 b.Status.SetCondition(&duckv1alpha1.Condition{ 518 Type: buildv1alpha1.BuildSucceeded, 519 Status: corev1.ConditionFalse, 520 Message: "hello", 521 }) 522 b.Status.StartTime = now 523 b.Status.CompletionTime = now 524 return b 525 }(), 526 expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob { 527 pj.Status = prowjobv1.ProwJobStatus{ 528 StartTime: now, 529 CompletionTime: &now, 530 State: prowjobv1.FailureState, 531 Description: "hello", 532 } 533 return pj 534 }, 535 expectedBuild: noBuildChange, 536 }, 537 { 538 name: "error when we cannot get prowjob", 539 namespace: errorGetProwJob, 540 err: true, 541 observedJob: &prowjobv1.ProwJob{ 542 Spec: prowjobv1.ProwJobSpec{ 543 Agent: prowjobv1.KnativeBuildAgent, 544 BuildSpec: &buildSpec, 545 }, 546 Status: prowjobv1.ProwJobStatus{ 547 State: prowjobv1.PendingState, 548 Description: "fancy", 549 }, 550 }, 551 }, 552 { 553 name: "error when we cannot get build", 554 namespace: errorGetBuild, 555 err: true, 556 observedBuild: func() *buildv1alpha1.Build { 557 pj := prowjobv1.ProwJob{} 558 pj.Spec.Type = prowjobv1.PeriodicJob 559 pj.Spec.Agent = prowjobv1.KnativeBuildAgent 560 pj.Spec.BuildSpec = &buildSpec 561 b, err := makeBuild(pj, "-72") 562 if err != nil { 563 panic(err) 564 } 565 b.Status.SetCondition(&duckv1alpha1.Condition{ 566 Type: buildv1alpha1.BuildSucceeded, 567 Status: corev1.ConditionTrue, 568 Message: "hello", 569 }) 570 b.Status.CompletionTime = now 571 b.Status.StartTime = now 572 return b 573 }(), 574 }, 575 { 576 name: "error when we cannot delete build", 577 namespace: errorDeleteBuild, 578 err: true, 579 observedBuild: func() *buildv1alpha1.Build { 580 pj := prowjobv1.ProwJob{} 581 pj.Spec.Type = prowjobv1.PeriodicJob 582 pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{} 583 b, err := makeBuild(pj, "44") 584 if err != nil { 585 panic(err) 586 } 587 return b 588 }(), 589 }, 590 { 591 name: "error when we cannot create build", 592 namespace: errorCreateBuild, 593 err: true, 594 observedJob: &prowjobv1.ProwJob{ 595 Spec: prowjobv1.ProwJobSpec{ 596 Agent: prowjobv1.KnativeBuildAgent, 597 BuildSpec: &buildSpec, 598 }, 599 }, 600 expectedJob: func(pj prowjobv1.ProwJob, _ buildv1alpha1.Build) prowjobv1.ProwJob { 601 pj.Status = prowjobv1.ProwJobStatus{ 602 StartTime: now, 603 State: prowjobv1.TriggeredState, 604 Description: descScheduling, 605 } 606 return pj 607 }, 608 }, 609 { 610 name: "error when buildspec is nil", 611 err: true, 612 observedJob: &prowjobv1.ProwJob{ 613 Spec: prowjobv1.ProwJobSpec{ 614 Agent: prowjobv1.KnativeBuildAgent, 615 BuildSpec: nil, 616 }, 617 Status: prowjobv1.ProwJobStatus{ 618 State: prowjobv1.TriggeredState, 619 }, 620 }, 621 }, 622 { 623 name: "error when we cannot update prowjob", 624 namespace: errorUpdateProwJob, 625 err: true, 626 observedJob: &prowjobv1.ProwJob{ 627 Spec: prowjobv1.ProwJobSpec{ 628 Agent: prowjobv1.KnativeBuildAgent, 629 BuildSpec: &buildSpec, 630 }, 631 Status: prowjobv1.ProwJobStatus{ 632 State: prowjobv1.PendingState, 633 Description: "fancy", 634 }, 635 }, 636 observedBuild: func() *buildv1alpha1.Build { 637 pj := prowjobv1.ProwJob{} 638 pj.Spec.Type = prowjobv1.PeriodicJob 639 pj.Spec.Agent = prowjobv1.KnativeBuildAgent 640 pj.Spec.BuildSpec = &buildSpec 641 b, err := makeBuild(pj, "42") 642 if err != nil { 643 panic(err) 644 } 645 b.Status.SetCondition(&duckv1alpha1.Condition{ 646 Type: buildv1alpha1.BuildSucceeded, 647 Status: corev1.ConditionTrue, 648 Message: "hello", 649 }) 650 b.Status.CompletionTime = now 651 b.Status.StartTime = now 652 return b 653 }(), 654 }} 655 656 for _, tc := range cases { 657 t.Run(tc.name, func(t *testing.T) { 658 name := "the-object-name" 659 // prowjobs all live in the same ns, so use name for injecting errors 660 if tc.namespace == errorGetProwJob { 661 name = errorGetProwJob 662 } else if tc.namespace == errorUpdateProwJob { 663 name = errorUpdateProwJob 664 } 665 bk := toKey(tc.context, tc.namespace, name) 666 jk := toKey(fakePJCtx, fakePJNS, name) 667 r := &fakeReconciler{ 668 jobs: map[string]prowjobv1.ProwJob{}, 669 builds: map[string]buildv1alpha1.Build{}, 670 nows: now, 671 } 672 if j := tc.observedJob; j != nil { 673 j.Name = name 674 j.Spec.Type = prowjobv1.PeriodicJob 675 r.jobs[jk] = *j 676 } 677 if b := tc.observedBuild; b != nil { 678 b.Name = name 679 r.builds[bk] = *b 680 } 681 expectedJobs := map[string]prowjobv1.ProwJob{} 682 if j := tc.expectedJob; j != nil { 683 expectedJobs[jk] = j(r.jobs[jk], r.builds[bk]) 684 } 685 expectedBuilds := map[string]buildv1alpha1.Build{} 686 if b := tc.expectedBuild; b != nil { 687 expectedBuilds[bk] = b(r.jobs[jk], r.builds[bk]) 688 } 689 err := reconcile(r, bk) 690 switch { 691 case err != nil: 692 if !tc.err { 693 t.Errorf("unexpected error: %v", err) 694 } 695 case tc.err: 696 t.Error("failed to receive expected error") 697 case !equality.Semantic.DeepEqual(r.jobs, expectedJobs): 698 t.Errorf("prowjobs do not match:\n%s", diff.ObjectReflectDiff(expectedJobs, r.jobs)) 699 case !equality.Semantic.DeepEqual(r.builds, expectedBuilds): 700 t.Errorf("builds do not match:\n%s", diff.ObjectReflectDiff(expectedBuilds, r.builds)) 701 } 702 }) 703 } 704 705 } 706 707 func TestDefaultArguments(t *testing.T) { 708 cases := []struct { 709 name string 710 t buildv1alpha1.TemplateInstantiationSpec 711 env map[string]string 712 expected buildv1alpha1.TemplateInstantiationSpec 713 }{ 714 { 715 name: "nothing set works", 716 }, 717 { 718 name: "add env", 719 env: map[string]string{ 720 "hello": "world", 721 }, 722 expected: buildv1alpha1.TemplateInstantiationSpec{ 723 Arguments: []buildv1alpha1.ArgumentSpec{{Name: "hello", Value: "world"}}, 724 }, 725 }, 726 { 727 name: "do not override env", 728 t: buildv1alpha1.TemplateInstantiationSpec{ 729 Arguments: []buildv1alpha1.ArgumentSpec{ 730 {Name: "ignore", Value: "this"}, 731 {Name: "keep", Value: "original value"}, 732 }, 733 }, 734 env: map[string]string{ 735 "hello": "world", 736 "keep": "should not see this", 737 }, 738 expected: buildv1alpha1.TemplateInstantiationSpec{ 739 Arguments: []buildv1alpha1.ArgumentSpec{ 740 {Name: "ignore", Value: "this"}, 741 {Name: "keep", Value: "original value"}, 742 {Name: "hello", Value: "world"}, 743 }, 744 }, 745 }, 746 } 747 748 for _, tc := range cases { 749 t.Run(tc.name, func(t *testing.T) { 750 templ := tc.t 751 defaultArguments(&templ, tc.env) 752 if !equality.Semantic.DeepEqual(templ, tc.expected) { 753 t.Errorf("builds do not match:\n%s", diff.ObjectReflectDiff(&tc.expected, templ)) 754 } 755 756 }) 757 } 758 } 759 760 func TestDefaultEnv(t *testing.T) { 761 cases := []struct { 762 name string 763 c corev1.Container 764 env map[string]string 765 expected corev1.Container 766 }{ 767 { 768 name: "nothing set works", 769 }, 770 { 771 name: "add env", 772 env: map[string]string{ 773 "hello": "world", 774 }, 775 expected: corev1.Container{ 776 Env: []corev1.EnvVar{{Name: "hello", Value: "world"}}, 777 }, 778 }, 779 { 780 name: "do not override env", 781 c: corev1.Container{ 782 Env: []corev1.EnvVar{ 783 {Name: "ignore", Value: "this"}, 784 {Name: "keep", Value: "original value"}, 785 }, 786 }, 787 env: map[string]string{ 788 "hello": "world", 789 "keep": "should not see this", 790 }, 791 expected: corev1.Container{ 792 Env: []corev1.EnvVar{ 793 {Name: "ignore", Value: "this"}, 794 {Name: "keep", Value: "original value"}, 795 {Name: "hello", Value: "world"}, 796 }, 797 }, 798 }, 799 } 800 801 for _, tc := range cases { 802 t.Run(tc.name, func(t *testing.T) { 803 c := tc.c 804 defaultEnv(&c, tc.env) 805 if !equality.Semantic.DeepEqual(c, tc.expected) { 806 t.Errorf("builds do not match:\n%s", diff.ObjectReflectDiff(&tc.expected, c)) 807 } 808 }) 809 } 810 } 811 812 func TestInjectSource(t *testing.T) { 813 cases := []struct { 814 name string 815 build buildv1alpha1.Build 816 pj prowjobv1.ProwJob 817 expected func(*buildv1alpha1.Build, prowjobv1.ProwJob) 818 err bool 819 }{ 820 { 821 name: "do nothing when source is set", 822 build: buildv1alpha1.Build{ 823 Spec: buildv1alpha1.BuildSpec{ 824 Source: &buildv1alpha1.SourceSpec{}, 825 }, 826 }, 827 }, 828 { 829 name: "do nothing when no refs are set", 830 pj: prowjobv1.ProwJob{ 831 Spec: prowjobv1.ProwJobSpec{ 832 Type: prowjobv1.PeriodicJob, 833 }, 834 }, 835 }, 836 { 837 name: "inject source, volumes, workdir when refs are set", 838 build: buildv1alpha1.Build{ 839 Spec: buildv1alpha1.BuildSpec{ 840 Steps: []corev1.Container{ 841 {}, // Override 842 {WorkingDir: "do not override"}, 843 }, 844 Template: &buildv1alpha1.TemplateInstantiationSpec{}, 845 }, 846 }, 847 pj: prowjobv1.ProwJob{ 848 Spec: prowjobv1.ProwJobSpec{ 849 ExtraRefs: []prowjobv1.Refs{{Org: "hi", Repo: "there"}}, 850 DecorationConfig: &prowjobv1.DecorationConfig{ 851 UtilityImages: &prowjobv1.UtilityImages{}, 852 }, 853 }, 854 }, 855 expected: func(b *buildv1alpha1.Build, pj prowjobv1.ProwJob) { 856 src, _, cloneVolumes, err := decorate.CloneRefs(pj, codeMount, logMount) 857 if err != nil { 858 t.Fatalf("failed to make clonerefs container: %v", err) 859 } 860 src.Name = "" 861 b.Spec.Volumes = append(b.Spec.Volumes, cloneVolumes...) 862 b.Spec.Source = &buildv1alpha1.SourceSpec{ 863 Custom: src, 864 } 865 wd := workDir(pj.Spec.ExtraRefs[0]) 866 b.Spec.Template.Arguments = append(b.Spec.Template.Arguments, wd) 867 b.Spec.Steps[0].WorkingDir = wd.Value 868 869 }, 870 }, 871 } 872 873 for _, tc := range cases { 874 t.Run(tc.name, func(t *testing.T) { 875 expected := tc.build 876 if tc.expected != nil { 877 tc.expected(&expected, tc.pj) 878 } 879 880 actual := &tc.build 881 err := injectSource(actual, tc.pj) 882 switch { 883 case err != nil: 884 if !tc.err { 885 t.Errorf("unexpected error: %v", err) 886 } 887 case tc.err: 888 t.Error("failed to return expected error") 889 case !equality.Semantic.DeepEqual(actual, &expected): 890 t.Errorf("builds do not match:\n%s", diff.ObjectReflectDiff(&expected, actual)) 891 } 892 }) 893 } 894 } 895 896 func TestBuildMeta(t *testing.T) { 897 cases := []struct { 898 name string 899 pj prowjobv1.ProwJob 900 expected func(prowjobv1.ProwJob, *metav1.ObjectMeta) 901 }{ 902 { 903 name: "Use pj.Spec.Namespace for build namespace", 904 pj: prowjobv1.ProwJob{ 905 ObjectMeta: metav1.ObjectMeta{ 906 Name: "whatever", 907 Namespace: "wrong", 908 }, 909 Spec: prowjobv1.ProwJobSpec{ 910 Namespace: "correct", 911 }, 912 }, 913 expected: func(pj prowjobv1.ProwJob, meta *metav1.ObjectMeta) { 914 meta.Name = pj.Name 915 meta.Namespace = pj.Spec.Namespace 916 meta.Labels, meta.Annotations = decorate.LabelsAndAnnotationsForJob(pj) 917 }, 918 }, 919 } 920 921 for _, tc := range cases { 922 t.Run(tc.name, func(t *testing.T) { 923 var expected metav1.ObjectMeta 924 tc.expected(tc.pj, &expected) 925 actual := buildMeta(tc.pj) 926 if !equality.Semantic.DeepEqual(actual, expected) { 927 t.Errorf("build meta does not match:\n%s", diff.ObjectReflectDiff(expected, actual)) 928 } 929 }) 930 } 931 } 932 933 func TestMakeBuild(t *testing.T) { 934 cases := []struct { 935 name string 936 job func(prowjobv1.ProwJob) prowjobv1.ProwJob 937 err bool 938 }{ 939 { 940 name: "reject empty prow job", 941 job: func(_ prowjobv1.ProwJob) prowjobv1.ProwJob { return prowjobv1.ProwJob{} }, 942 err: true, 943 }, 944 { 945 name: "return valid build with valid prowjob", 946 }, 947 { 948 name: "configure source when refs are set", 949 job: func(pj prowjobv1.ProwJob) prowjobv1.ProwJob { 950 pj.Spec.ExtraRefs = []prowjobv1.Refs{{Org: "bonus"}} 951 pj.Spec.DecorationConfig = &prowjobv1.DecorationConfig{ 952 UtilityImages: &prowjobv1.UtilityImages{}, 953 } 954 return pj 955 }, 956 }, 957 { 958 name: "do not override source when set", 959 job: func(pj prowjobv1.ProwJob) prowjobv1.ProwJob { 960 pj.Spec.ExtraRefs = []prowjobv1.Refs{{Org: "bonus"}} 961 pj.Spec.DecorationConfig = &prowjobv1.DecorationConfig{ 962 UtilityImages: &prowjobv1.UtilityImages{}, 963 } 964 pj.Spec.BuildSpec.Source = &buildv1alpha1.SourceSpec{} 965 return pj 966 }, 967 }, 968 } 969 970 for _, tc := range cases { 971 t.Run(tc.name, func(t *testing.T) { 972 pj := prowjobv1.ProwJob{} 973 pj.Name = "world" 974 pj.Namespace = "hello" 975 pj.Spec.Type = prowjobv1.PeriodicJob 976 pj.Spec.BuildSpec = &buildv1alpha1.BuildSpec{} 977 pj.Spec.BuildSpec.Steps = append(pj.Spec.BuildSpec.Steps, corev1.Container{}) 978 pj.Spec.BuildSpec.Template = &buildv1alpha1.TemplateInstantiationSpec{} 979 if tc.job != nil { 980 pj = tc.job(pj) 981 } 982 const randomBuildID = "so-many-builds" 983 actual, err := makeBuild(pj, randomBuildID) 984 if err != nil { 985 if !tc.err { 986 t.Errorf("unexpected error: %v", err) 987 } 988 return 989 } else if tc.err { 990 t.Error("failed to receive expected error") 991 } 992 expected := buildv1alpha1.Build{ 993 ObjectMeta: buildMeta(pj), 994 Spec: *pj.Spec.BuildSpec, 995 } 996 env, err := buildEnv(pj, randomBuildID) 997 if err != nil { 998 t.Fatalf("failed to create expected build env: %v", err) 999 } 1000 injectEnvironment(&expected, env) 1001 err = injectSource(&expected, pj) 1002 if err != nil { 1003 t.Fatalf("failed to inject expected source: %v", err) 1004 } 1005 if !equality.Semantic.DeepEqual(actual, &expected) { 1006 t.Errorf("builds do not match:\n%s", diff.ObjectReflectDiff(&expected, actual)) 1007 } 1008 }) 1009 } 1010 } 1011 1012 func TestDescription(t *testing.T) { 1013 cases := []struct { 1014 name string 1015 message string 1016 reason string 1017 fallback string 1018 expected string 1019 }{ 1020 { 1021 name: "prefer message over reason or fallback", 1022 message: "hello", 1023 reason: "world", 1024 fallback: "doh", 1025 expected: "hello", 1026 }, 1027 { 1028 name: "prefer reason over fallback", 1029 reason: "world", 1030 fallback: "other", 1031 expected: "world", 1032 }, 1033 { 1034 name: "use fallback if nothing else set", 1035 fallback: "fancy", 1036 expected: "fancy", 1037 }, 1038 } 1039 1040 for _, tc := range cases { 1041 bc := duckv1alpha1.Condition{ 1042 Message: tc.message, 1043 Reason: tc.reason, 1044 } 1045 if actual := description(bc, tc.fallback); actual != tc.expected { 1046 t.Errorf("%s: actual %q != expected %q", tc.name, actual, tc.expected) 1047 } 1048 } 1049 } 1050 1051 func TestProwJobStatus(t *testing.T) { 1052 now := metav1.Now() 1053 later := metav1.NewTime(now.Time.Add(1 * time.Hour)) 1054 cases := []struct { 1055 name string 1056 input buildv1alpha1.BuildStatus 1057 state prowjobv1.ProwJobState 1058 desc string 1059 fallback string 1060 }{ 1061 { 1062 name: "empty conditions returns triggered/scheduling", 1063 state: prowjobv1.TriggeredState, 1064 desc: descScheduling, 1065 }, 1066 { 1067 name: "truly succeeded state returns success", 1068 input: buildv1alpha1.BuildStatus{ 1069 Conditions: []duckv1alpha1.Condition{ 1070 { 1071 Type: buildv1alpha1.BuildSucceeded, 1072 Status: corev1.ConditionTrue, 1073 Message: "fancy", 1074 }, 1075 }, 1076 }, 1077 state: prowjobv1.SuccessState, 1078 desc: "fancy", 1079 fallback: descSucceeded, 1080 }, 1081 { 1082 name: "falsely succeeded state returns failure", 1083 input: buildv1alpha1.BuildStatus{ 1084 Conditions: []duckv1alpha1.Condition{ 1085 { 1086 Type: buildv1alpha1.BuildSucceeded, 1087 Status: corev1.ConditionFalse, 1088 Message: "weird", 1089 }, 1090 }, 1091 }, 1092 state: prowjobv1.FailureState, 1093 desc: "weird", 1094 fallback: descFailed, 1095 }, 1096 { 1097 name: "unstarted job returns triggered/initializing", 1098 input: buildv1alpha1.BuildStatus{ 1099 Conditions: []duckv1alpha1.Condition{ 1100 { 1101 Type: buildv1alpha1.BuildSucceeded, 1102 Status: corev1.ConditionUnknown, 1103 Message: "hola", 1104 }, 1105 }, 1106 }, 1107 state: prowjobv1.TriggeredState, 1108 desc: "hola", 1109 fallback: descInitializing, 1110 }, 1111 { 1112 name: "unfinished job returns running", 1113 input: buildv1alpha1.BuildStatus{ 1114 StartTime: now, 1115 Conditions: []duckv1alpha1.Condition{ 1116 { 1117 Type: buildv1alpha1.BuildSucceeded, 1118 Status: corev1.ConditionUnknown, 1119 Message: "hola", 1120 }, 1121 }, 1122 }, 1123 state: prowjobv1.PendingState, 1124 desc: "hola", 1125 fallback: descRunning, 1126 }, 1127 { 1128 name: "builds with unknown success status are still running", 1129 input: buildv1alpha1.BuildStatus{ 1130 StartTime: now, 1131 CompletionTime: later, 1132 Conditions: []duckv1alpha1.Condition{ 1133 { 1134 Type: buildv1alpha1.BuildSucceeded, 1135 Status: corev1.ConditionUnknown, 1136 Message: "hola", 1137 }, 1138 }, 1139 }, 1140 state: prowjobv1.PendingState, 1141 desc: "hola", 1142 fallback: descRunning, 1143 }, 1144 { 1145 name: "completed builds without a succeeded condition end in error", 1146 input: buildv1alpha1.BuildStatus{ 1147 StartTime: now, 1148 CompletionTime: later, 1149 }, 1150 state: prowjobv1.ErrorState, 1151 desc: descMissingCondition, 1152 }, 1153 } 1154 1155 for _, tc := range cases { 1156 if len(tc.fallback) > 0 { 1157 tc.desc = tc.fallback 1158 tc.fallback = "" 1159 tc.name += " [fallback]" 1160 cond := tc.input.Conditions[0] 1161 cond.Message = "" 1162 tc.input.Conditions = []duckv1alpha1.Condition{cond} 1163 cases = append(cases, tc) 1164 } 1165 } 1166 1167 for _, tc := range cases { 1168 t.Run(tc.name, func(t *testing.T) { 1169 state, desc := prowJobStatus(tc.input) 1170 if state != tc.state { 1171 t.Errorf("state %q != expected %q", state, tc.state) 1172 } 1173 if desc != tc.desc { 1174 t.Errorf("description %q != expected %q", desc, tc.desc) 1175 } 1176 }) 1177 } 1178 }