knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/condition_set_impl_test.go (about) 1 /* 2 Copyright 2019 The Knative 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 apis 18 19 import ( 20 "testing" 21 "time" 22 23 "github.com/google/go-cmp/cmp" 24 "github.com/google/go-cmp/cmp/cmpopts" 25 corev1 "k8s.io/api/core/v1" 26 "k8s.io/apimachinery/pkg/api/equality" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 ) 29 30 // TestStatus is to validate ConditionAccessor interface works 31 type TestStatus struct { 32 c Conditions 33 } 34 35 func (t *TestStatus) GetConditions() Conditions { 36 return t.c 37 } 38 39 func (t *TestStatus) SetConditions(conditions Conditions) { 40 t.c = conditions 41 } 42 43 var ignoreFields = cmpopts.IgnoreFields(Condition{}, "LastTransitionTime", "Severity") 44 45 func TestGetCondition(t *testing.T) { 46 condSet := NewLivingConditionSet() 47 cases := []struct { 48 name string 49 status ConditionsAccessor 50 get ConditionType 51 expect *Condition 52 }{{ 53 name: "simple", 54 status: &TestStatus{c: Conditions{{ 55 Type: ConditionReady, 56 Status: corev1.ConditionTrue, 57 }}}, 58 get: ConditionReady, 59 expect: &Condition{ 60 Type: ConditionReady, 61 Status: corev1.ConditionTrue, 62 }, 63 }, { 64 name: "nil", 65 status: nil, 66 get: ConditionReady, 67 expect: nil, 68 }, { 69 name: "missing", 70 status: &TestStatus{c: Conditions{{ 71 Type: ConditionReady, 72 Status: corev1.ConditionTrue, 73 }}}, 74 get: "Missing", 75 expect: nil, 76 }} 77 for _, tc := range cases { 78 t.Run(tc.name, func(t *testing.T) { 79 e, a := tc.expect, condSet.Manage(tc.status).GetCondition(tc.get) 80 if diff := cmp.Diff(e, a, ignoreFields); diff != "" { 81 t.Errorf("%s (-want, +got) = %v", tc.name, diff) 82 } 83 }) 84 } 85 } 86 87 func TestSetCondition(t *testing.T) { 88 condSet := NewLivingConditionSet() 89 cases := []struct { 90 name string 91 status ConditionsAccessor 92 set Condition 93 expect *Condition 94 }{{ 95 name: "simple", 96 status: &TestStatus{c: Conditions{{ 97 Type: ConditionReady, 98 Status: corev1.ConditionFalse, 99 }}}, 100 set: Condition{ 101 Type: ConditionReady, 102 Status: corev1.ConditionTrue, 103 }, 104 expect: &Condition{ 105 Type: ConditionReady, 106 Status: corev1.ConditionTrue, 107 }, 108 }, { 109 name: "nil", 110 status: nil, 111 set: Condition{ 112 Type: ConditionReady, 113 Status: corev1.ConditionTrue, 114 }, 115 expect: nil, 116 }, { 117 name: "empty", 118 status: &TestStatus{}, 119 set: Condition{ 120 Type: ConditionReady, 121 Status: corev1.ConditionTrue, 122 }, 123 expect: &Condition{ 124 Type: ConditionReady, 125 Status: corev1.ConditionTrue, 126 }, 127 }} 128 for _, tc := range cases { 129 t.Run(tc.name, func(t *testing.T) { 130 condSet.Manage(tc.status).SetCondition(tc.set) 131 e, a := tc.expect, condSet.Manage(tc.status).GetCondition(tc.set.Type) 132 if diff := cmp.Diff(e, a, ignoreFields); diff != "" { 133 t.Errorf("%s (-want, +got) = %v", tc.name, diff) 134 } 135 }) 136 } 137 } 138 139 func TestIsHappy(t *testing.T) { 140 cases := []struct { 141 name string 142 status ConditionsAccessor 143 condSet ConditionSet 144 isHappy bool 145 }{{ 146 name: "empty accessor should not be ready", 147 status: &TestStatus{ 148 c: Conditions(nil), 149 }, 150 condSet: NewLivingConditionSet(), 151 isHappy: false, 152 }, { 153 name: "Different condition type should not be ready", 154 status: &TestStatus{ 155 c: Conditions{{ 156 Type: "Foo", 157 Status: corev1.ConditionTrue, 158 }}, 159 }, 160 condSet: NewLivingConditionSet(), 161 isHappy: false, 162 }, { 163 name: "False condition accessor should not be ready", 164 status: &TestStatus{ 165 c: Conditions{{ 166 Type: ConditionReady, 167 Status: corev1.ConditionFalse, 168 }}, 169 }, 170 condSet: NewLivingConditionSet(), 171 isHappy: false, 172 }, { 173 name: "Unknown condition accessor should not be ready", 174 status: &TestStatus{ 175 c: Conditions{{ 176 Type: ConditionReady, 177 Status: corev1.ConditionUnknown, 178 }}, 179 }, 180 condSet: NewLivingConditionSet(), 181 isHappy: false, 182 }, { 183 name: "Missing condition accessor should not be ready", 184 status: &TestStatus{ 185 c: Conditions{{ 186 Type: ConditionReady, 187 }}, 188 }, 189 condSet: NewLivingConditionSet(), 190 isHappy: false, 191 }, { 192 name: "True condition accessor should be ready", 193 status: &TestStatus{ 194 c: Conditions{{ 195 Type: ConditionReady, 196 Status: corev1.ConditionTrue, 197 }}, 198 }, 199 condSet: NewLivingConditionSet(), 200 isHappy: true, 201 }, { 202 name: "Multiple conditions with ready accessor should be ready", 203 status: &TestStatus{ 204 c: Conditions{{ 205 Type: "Foo", 206 Status: corev1.ConditionTrue, 207 }, { 208 Type: ConditionReady, 209 Status: corev1.ConditionTrue, 210 }}, 211 }, 212 condSet: NewLivingConditionSet(), 213 isHappy: true, 214 }, { 215 name: "Multiple conditions with ready accessor false should not be ready", 216 status: &TestStatus{ 217 c: Conditions{{ 218 Type: "Foo", 219 Status: corev1.ConditionTrue, 220 }, { 221 Type: ConditionReady, 222 Status: corev1.ConditionFalse, 223 }}, 224 }, 225 condSet: NewLivingConditionSet(), 226 isHappy: false, 227 }, { 228 name: "Multiple conditions with mixed ready accessor, some don't matter, ready", 229 status: &TestStatus{ 230 c: Conditions{{ 231 Type: "Foo", 232 Status: corev1.ConditionTrue, 233 }, { 234 Type: "Bar", 235 Status: corev1.ConditionFalse, 236 }, { 237 Type: ConditionReady, 238 Status: corev1.ConditionTrue, 239 }}, 240 }, 241 condSet: NewLivingConditionSet(), 242 isHappy: true, 243 }, { 244 name: "Multiple conditions with mixed ready accessor, some don't matter, not ready", 245 status: &TestStatus{ 246 c: Conditions{{ 247 Type: "Foo", 248 Status: corev1.ConditionTrue, 249 }, { 250 Type: "Bar", 251 Status: corev1.ConditionTrue, 252 }, { 253 Type: ConditionReady, 254 Status: corev1.ConditionFalse, 255 }}, 256 }, 257 condSet: NewLivingConditionSet(), 258 isHappy: false, 259 }} 260 261 for _, tc := range cases { 262 t.Run(tc.name, func(t *testing.T) { 263 if e, a := tc.isHappy, tc.condSet.Manage(tc.status).IsHappy(); e != a { 264 t.Errorf("%q expected: %v got: %v", tc.name, e, a) 265 } 266 }) 267 } 268 } 269 270 func TestUpdateLastTransitionTime(t *testing.T) { 271 condSet := NewLivingConditionSet() 272 273 cases := []struct { 274 name string 275 conditions Conditions 276 condition Condition 277 update bool 278 }{{ 279 name: "LastTransitionTime should be set", 280 conditions: Conditions{{ 281 Type: ConditionReady, 282 Status: corev1.ConditionFalse, 283 }}, 284 285 condition: Condition{ 286 Type: ConditionReady, 287 Status: corev1.ConditionTrue, 288 }, 289 update: true, 290 }, { 291 name: "LastTransitionTime should update", 292 conditions: Conditions{{ 293 Type: ConditionReady, 294 Status: corev1.ConditionFalse, 295 LastTransitionTime: VolatileTime{metav1.NewTime(time.Unix(1337, 0))}, 296 }}, 297 condition: Condition{ 298 Type: ConditionReady, 299 Status: corev1.ConditionTrue, 300 }, 301 update: true, 302 }, { 303 name: "if LastTransitionTime is the only chance, don't do it", 304 conditions: Conditions{{ 305 Type: ConditionReady, 306 Status: corev1.ConditionFalse, 307 LastTransitionTime: VolatileTime{metav1.NewTime(time.Unix(1337, 0))}, 308 }}, 309 310 condition: Condition{ 311 Type: ConditionReady, 312 Status: corev1.ConditionFalse, 313 }, 314 update: false, 315 }} 316 317 for _, tc := range cases { 318 t.Run(tc.name, func(t *testing.T) { 319 conds := &TestStatus{c: tc.conditions} 320 321 was := condSet.Manage(conds).GetCondition(tc.condition.Type) 322 condSet.Manage(conds).SetCondition(tc.condition) 323 now := condSet.Manage(conds).GetCondition(tc.condition.Type) 324 325 if e, a := tc.condition.Status, now.Status; e != a { 326 t.Errorf("%q expected: %v to match %v", tc.name, e, a) 327 } 328 329 if tc.update { 330 if e, a := was.LastTransitionTime, now.LastTransitionTime; e == a { 331 t.Errorf("%q expected: %v to not match %v", tc.name, e, a) 332 } 333 } else { 334 if e, a := was.LastTransitionTime, now.LastTransitionTime; e != a { 335 t.Errorf("%q expected: %v to match %v", tc.name, e, a) 336 } 337 } 338 }) 339 } 340 } 341 342 func TestResourceConditions(t *testing.T) { 343 condSet := NewLivingConditionSet() 344 345 status := &TestStatus{} 346 347 foo := Condition{ 348 Type: "Foo", 349 Status: "True", 350 } 351 bar := Condition{ 352 Type: "Bar", 353 Status: "True", 354 } 355 356 // Add a new condition. 357 condSet.Manage(status).SetCondition(foo) 358 359 if got, want := len(status.c), 1; got != want { 360 t.Fatalf("Unexpected Condition length; got %d, want %d", got, want) 361 } 362 363 // Add a second condition. 364 condSet.Manage(status).SetCondition(bar) 365 366 if got, want := len(status.c), 2; got != want { 367 t.Fatalf("Unexpected Condition length; got %d, want %d", got, want) 368 } 369 } 370 371 func TestConditionSeverity(t *testing.T) { 372 condSet := NewLivingConditionSet("Foo") 373 status := &TestStatus{} 374 375 // Add a new condition. 376 condSet.Manage(status).InitializeConditions() 377 378 if got, want := len(status.c), 2; got != want { 379 t.Errorf("Unexpected number of conditions: %d, wanted %d", got, want) 380 } 381 382 condSet.Manage(status).MarkFalse("Bar", "", "") 383 384 if got, want := len(status.c), 3; got != want { 385 t.Errorf("Unexpected number of conditions: %d, wanted %d", got, want) 386 } 387 388 if got, want := condSet.Manage(status).GetCondition("Ready").Severity, ConditionSeverityError; got != want { 389 t.Errorf("GetCondition(%q).Severity = %v, wanted %v", "Ready", got, want) 390 } 391 392 if got, want := condSet.Manage(status).GetCondition("Foo").Severity, ConditionSeverityError; got != want { 393 t.Errorf("GetCondition(%q).Severity = %v, wanted %v", "Foo", got, want) 394 } 395 396 if got, want := condSet.Manage(status).GetCondition("Bar").Severity, ConditionSeverityInfo; got != want { 397 t.Errorf("GetCondition(%q).Severity = %v, wanted %v", "Bar", got, want) 398 } 399 } 400 401 // getTypes is a small helped to strip out the used ConditionTypes from Conditions 402 func getTypes(conds Conditions) []ConditionType { 403 types := make([]ConditionType, 0, len(conds)) 404 for _, c := range conds { 405 types = append(types, c.Type) 406 } 407 return types 408 } 409 410 type ConditionMarkTrueTest struct { 411 name string 412 conditions Conditions 413 conditionTypes []ConditionType 414 mark ConditionType 415 happy bool 416 happyWant *Condition 417 } 418 419 func doTestMarkTrueAccessor(t *testing.T, cases []ConditionMarkTrueTest) { 420 for _, tc := range cases { 421 t.Run(tc.name, func(t *testing.T) { 422 conditionTypes := tc.conditionTypes 423 if conditionTypes == nil { 424 conditionTypes = getTypes(tc.conditions) 425 } 426 condSet := NewLivingConditionSet(conditionTypes...) 427 status := &TestStatus{c: tc.conditions} 428 condSet.Manage(status).InitializeConditions() 429 430 condSet.Manage(status).MarkTrue(tc.mark) 431 432 if e, a := tc.happy, condSet.Manage(status).IsHappy(); e != a { 433 t.Errorf("%q expected: %v got: %v", tc.name, e, a) 434 } else if !e && tc.happyWant != nil { 435 e, a := tc.happyWant, condSet.Manage(status).GetTopLevelCondition() 436 if diff := cmp.Diff(e, a, ignoreFields); diff != "" { 437 t.Errorf("%s (-want, +got) = %v", tc.name, diff) 438 } 439 } 440 441 if tc.mark == condSet.happy { 442 // Skip validation the happy condition because we can't be sure 443 // marking it true was correct. Use tc.happyWant to test that case. 444 return 445 } 446 447 expected := &Condition{ 448 Type: tc.mark, 449 Status: corev1.ConditionTrue, 450 } 451 452 e, a := expected, condSet.Manage(status).GetCondition(tc.mark) 453 if diff := cmp.Diff(e, a, ignoreFields); diff != "" { 454 t.Errorf("%s (-want, +got) = %v", tc.name, diff) 455 } 456 }) 457 // Run same test with MarkTrueWithReason 458 t.Run(tc.name+" with reason", func(t *testing.T) { 459 conditionTypes := tc.conditionTypes 460 if conditionTypes == nil { 461 conditionTypes = getTypes(tc.conditions) 462 } 463 condSet := NewLivingConditionSet(conditionTypes...) 464 status := &TestStatus{c: tc.conditions} 465 condSet.Manage(status).InitializeConditions() 466 467 condSet.Manage(status).MarkTrueWithReason(tc.mark, "UnitTest", "calm down, just testing") 468 469 if e, a := tc.happy, condSet.Manage(status).IsHappy(); e != a { 470 t.Errorf("%q expected: %v got: %v", tc.name, e, a) 471 } else if !e && tc.happyWant != nil { 472 e, a := tc.happyWant, condSet.Manage(status).GetTopLevelCondition() 473 if diff := cmp.Diff(e, a, ignoreFields); diff != "" { 474 t.Errorf("%s (-want, +got) = %v", tc.name, diff) 475 } 476 } 477 478 if tc.mark == condSet.happy { 479 // Skip validation the happy condition because we can't be sure 480 // marking it true was correct. Use tc.happyWant to test that case. 481 return 482 } 483 484 expected := &Condition{ 485 Type: tc.mark, 486 Status: corev1.ConditionTrue, 487 Reason: "UnitTest", 488 Message: "calm down, just testing", 489 } 490 491 e, a := expected, condSet.Manage(status).GetCondition(tc.mark) 492 if diff := cmp.Diff(e, a, ignoreFields); diff != "" { 493 t.Errorf("%s (-want, +got) = %v", tc.name, diff) 494 } 495 }) 496 } 497 } 498 499 func TestMarkTrue(t *testing.T) { 500 cases := []ConditionMarkTrueTest{{ 501 name: "no deps", 502 mark: ConditionReady, 503 happy: true, 504 }, { 505 name: "existing conditions, turns happy", 506 conditions: Conditions{{ 507 Type: ConditionReady, 508 Status: corev1.ConditionFalse, 509 }}, 510 mark: ConditionReady, 511 happy: true, 512 }, { 513 name: "with deps, happy", 514 conditions: Conditions{{ 515 Type: ConditionReady, 516 Status: corev1.ConditionFalse, 517 }, { 518 Type: "Foo", 519 Status: corev1.ConditionTrue, 520 }}, 521 mark: ConditionReady, 522 happy: true, 523 }, { 524 name: "with deps, not happy", 525 conditions: Conditions{{ 526 Type: ConditionReady, 527 Status: corev1.ConditionFalse, 528 Reason: "ReadyReason", 529 Message: "ReadyMsg", 530 }, { 531 Type: "Foo", 532 Status: corev1.ConditionFalse, 533 Reason: "FooReason", 534 Message: "FooMsg", 535 }}, 536 mark: ConditionReady, 537 happy: false, 538 happyWant: &Condition{ 539 Type: ConditionReady, 540 Status: corev1.ConditionFalse, 541 Reason: "FooReason", 542 Message: "FooMsg", 543 }, 544 }, { 545 name: "update dep, turns happy", 546 conditions: Conditions{{ 547 Type: ConditionReady, 548 Status: corev1.ConditionFalse, 549 }, { 550 Type: "Foo", 551 Status: corev1.ConditionFalse, 552 }}, 553 mark: "Foo", 554 happy: true, 555 }, { 556 name: "update dep, happy was unknown, turns happy", 557 conditions: Conditions{{ 558 Type: ConditionReady, 559 Status: corev1.ConditionUnknown, 560 }, { 561 Type: "Foo", 562 Status: corev1.ConditionFalse, 563 }}, 564 mark: "Foo", 565 happy: true, 566 }, { 567 name: "update dep 1/2, still not happy", 568 conditions: Conditions{{ 569 Type: ConditionReady, 570 Status: corev1.ConditionFalse, 571 Reason: "FooReason", 572 Message: "FooMsg", 573 }, { 574 Type: "Foo", 575 Status: corev1.ConditionFalse, 576 Reason: "FooReason", 577 Message: "FooMsg", 578 }, { 579 Type: "Bar", 580 Status: corev1.ConditionFalse, 581 Reason: "BarReason", 582 Message: "BarMsg", 583 }}, 584 mark: "Foo", 585 happy: false, 586 happyWant: &Condition{ 587 Type: ConditionReady, 588 Status: corev1.ConditionFalse, 589 Reason: "BarReason", 590 Message: "BarMsg", 591 }, 592 }, { 593 name: "update dep 1/3, mixed status, still not happy", 594 conditions: Conditions{{ 595 Type: ConditionReady, 596 Status: corev1.ConditionFalse, 597 Reason: "FooReason", 598 Message: "FooMsg", 599 }, { 600 Type: "Foo", 601 Status: corev1.ConditionFalse, 602 Reason: "FooReason", 603 Message: "FooMsg", 604 }, { 605 Type: "Bar", 606 Status: corev1.ConditionUnknown, 607 Reason: "BarReason", 608 Message: "BarMsg", 609 }, { 610 Type: "Baz", 611 Status: corev1.ConditionFalse, 612 Reason: "BazReason", 613 Message: "BazMsg", 614 }}, 615 mark: "Foo", 616 happy: false, 617 happyWant: &Condition{ 618 Type: ConditionReady, 619 Status: corev1.ConditionFalse, 620 Reason: "BazReason", 621 Message: "BazMsg", 622 }, 623 }, { 624 name: "update dep 1/3, unknown status, still not happy", 625 conditions: Conditions{{ 626 Type: ConditionReady, 627 Status: corev1.ConditionFalse, 628 Reason: "FooReason", 629 Message: "FooMsg", 630 }, { 631 Type: "Foo", 632 Status: corev1.ConditionFalse, 633 Reason: "FooReason", 634 Message: "FooMsg", 635 }, { 636 Type: "Bar", 637 Status: corev1.ConditionUnknown, 638 Reason: "BarReason", 639 Message: "BarMsg", 640 }, { 641 Type: "Baz", 642 Status: corev1.ConditionUnknown, 643 Reason: "BazReason", 644 Message: "BazMsg", 645 }}, 646 mark: "Foo", 647 happy: false, 648 happyWant: &Condition{ 649 Type: ConditionReady, 650 Status: corev1.ConditionUnknown, 651 Reason: "BarReason", 652 Message: "BarMsg", 653 }, 654 }, { 655 name: "update dep 1/3, unknown status because nil", 656 conditions: Conditions{{ 657 Type: ConditionReady, 658 Status: corev1.ConditionFalse, 659 Reason: "FooReason", 660 Message: "FooMsg", 661 }, { 662 Type: "Foo", 663 Status: corev1.ConditionFalse, 664 Reason: "FooReason", 665 Message: "FooMsg", 666 }}, 667 mark: "Foo", 668 conditionTypes: []ConditionType{"Foo", "Bar", "Baz"}, 669 happy: false, 670 happyWant: &Condition{ 671 Type: ConditionReady, 672 Status: corev1.ConditionUnknown, 673 }, 674 }, { 675 name: "update deps all happy with extra cruft", 676 conditions: Conditions{{ 677 Type: ConditionReady, 678 Status: corev1.ConditionFalse, 679 Reason: "FooReason", 680 Message: "FooMsg", 681 }, { 682 Type: "Foo", 683 Status: corev1.ConditionFalse, 684 Reason: "FooReason", 685 Message: "FooMsg", 686 }, { 687 Type: "Bar", 688 Status: corev1.ConditionUnknown, 689 Reason: "BarReason", 690 Message: "BarMsg", 691 Severity: "FYI", 692 }, { 693 Type: "Baz", 694 Status: corev1.ConditionUnknown, 695 Reason: "BazReason", 696 Message: "BazMsg", 697 Severity: "LOLJK", 698 }}, 699 mark: "Foo", 700 conditionTypes: []ConditionType{"Foo"}, 701 happy: true, 702 happyWant: &Condition{ 703 Type: ConditionReady, 704 Status: corev1.ConditionTrue, 705 }, 706 }, { 707 name: "with no dependents with extra cruft", 708 conditions: Conditions{{ 709 Type: ConditionReady, 710 Status: corev1.ConditionFalse, 711 Reason: "LongStory", 712 Message: "Set manually", 713 }, { 714 Type: "Foo", 715 Status: corev1.ConditionFalse, 716 Reason: "FooReason", 717 Message: "FooMsg", 718 }, { 719 Type: "Bar", 720 Status: corev1.ConditionUnknown, 721 Reason: "BarReason", 722 Message: "BarMsg", 723 Severity: "FYI", 724 }, { 725 Type: "Baz", 726 Status: corev1.ConditionUnknown, 727 Reason: "BazReason", 728 Message: "BazMsg", 729 Severity: "LOLJK", 730 }}, 731 mark: "Foo", 732 conditionTypes: []ConditionType{}, 733 happy: true, 734 happyWant: &Condition{ 735 Type: ConditionReady, 736 Status: corev1.ConditionTrue, 737 }, 738 }, { 739 name: "happy dependents with extra cruft", 740 conditions: Conditions{{ 741 Type: ConditionReady, 742 Status: corev1.ConditionFalse, 743 Reason: "LongStory", 744 Message: "Set manually", 745 }, { 746 Type: "Foo", 747 Status: corev1.ConditionTrue, 748 }, { 749 Type: "Bar", 750 Status: corev1.ConditionUnknown, 751 Reason: "BarReason", 752 Message: "BarMsg", 753 Severity: "FYI", 754 }, { 755 Type: "Baz", 756 Status: corev1.ConditionUnknown, 757 Reason: "BazReason", 758 Message: "BazMsg", 759 Severity: "LOLJK", 760 }}, 761 mark: "Bar", 762 conditionTypes: []ConditionType{"Foo"}, 763 happy: true, 764 happyWant: &Condition{ 765 Type: ConditionReady, 766 Status: corev1.ConditionTrue, 767 }, 768 }, { 769 name: "all happy but not cover all dependents", 770 conditions: Conditions{{ 771 Type: ConditionReady, 772 Status: corev1.ConditionFalse, 773 Reason: "LongStory", 774 Message: "Set manually", 775 }, { 776 Type: "Foo", 777 Status: corev1.ConditionTrue, 778 }}, 779 mark: "Foo", 780 conditionTypes: []ConditionType{"Foo", "Bar"}, // dependents is more than conditions. 781 happy: false, 782 happyWant: &Condition{ 783 Type: ConditionReady, 784 Status: corev1.ConditionUnknown, 785 }, 786 }, { 787 name: "all happy and cover all dependents", 788 conditions: Conditions{{ 789 Type: ConditionReady, 790 Status: corev1.ConditionFalse, 791 Reason: "LongStory", 792 Message: "Set manually", 793 }, { 794 Type: "Foo", 795 Status: corev1.ConditionTrue, 796 }, { 797 Type: "NewCondition", 798 Status: corev1.ConditionTrue, 799 }}, 800 mark: "Foo", 801 conditionTypes: []ConditionType{"Foo"}, // dependents is less than conditions. 802 happy: true, 803 happyWant: &Condition{ 804 Type: ConditionReady, 805 Status: corev1.ConditionTrue, 806 }, 807 }} 808 doTestMarkTrueAccessor(t, cases) 809 } 810 811 type ConditionMarkFalseTest struct { 812 name string 813 conditions Conditions 814 mark ConditionType 815 unhappy bool 816 } 817 818 func doTestMarkFalseAccessor(t *testing.T, cases []ConditionMarkFalseTest) { 819 for _, tc := range cases { 820 t.Run(tc.name, func(t *testing.T) { 821 condSet := NewLivingConditionSet(getTypes(tc.conditions)...) 822 status := &TestStatus{c: tc.conditions} 823 condSet.Manage(status).InitializeConditions() 824 825 condSet.Manage(status).MarkFalse(tc.mark, "UnitTest", "calm down, just testing") 826 827 if e, a := !tc.unhappy, condSet.Manage(status).IsHappy(); e != a { 828 t.Errorf("%q expected: %v got: %v", tc.name, e, a) 829 } 830 831 expected := &Condition{ 832 Type: tc.mark, 833 Status: corev1.ConditionFalse, 834 Reason: "UnitTest", 835 Message: "calm down, just testing", 836 } 837 838 e, a := expected, condSet.Manage(status).GetCondition(tc.mark) 839 if diff := cmp.Diff(e, a, ignoreFields); diff != "" { 840 t.Errorf("%s (-want, +got) = %v", tc.name, diff) 841 } 842 }) 843 } 844 } 845 846 func TestMarkFalse(t *testing.T) { 847 cases := []ConditionMarkFalseTest{{ 848 name: "no deps", 849 mark: ConditionReady, 850 unhappy: true, 851 }, { 852 name: "existing conditions, turns unhappy", 853 conditions: Conditions{{ 854 Type: ConditionReady, 855 Status: corev1.ConditionTrue, 856 }}, 857 mark: ConditionReady, 858 unhappy: true, 859 }, { 860 name: "with deps, turns unhappy", 861 conditions: Conditions{{ 862 Type: ConditionReady, 863 Status: corev1.ConditionTrue, 864 }, { 865 Type: "Foo", 866 Status: corev1.ConditionTrue, 867 }}, 868 mark: ConditionReady, 869 unhappy: true, 870 }, { 871 name: "with deps, turns unhappy", 872 conditions: Conditions{{ 873 Type: ConditionReady, 874 Status: corev1.ConditionTrue, 875 }, { 876 Type: "Foo", 877 Status: corev1.ConditionFalse, 878 }}, 879 mark: ConditionReady, 880 unhappy: true, 881 }, { 882 name: "update dep, turns unhappy", 883 conditions: Conditions{{ 884 Type: ConditionReady, 885 Status: corev1.ConditionTrue, 886 }, { 887 Type: "Foo", 888 Status: corev1.ConditionTrue, 889 }}, 890 mark: "Foo", 891 unhappy: true, 892 }, { 893 name: "update dep, happy was unknown, turns unhappy", 894 conditions: Conditions{{ 895 Type: ConditionReady, 896 Status: corev1.ConditionUnknown, 897 }, { 898 Type: "Foo", 899 Status: corev1.ConditionFalse, 900 }}, 901 mark: "Foo", 902 unhappy: true, 903 }, { 904 name: "update dep 1/2, turns unhappy", 905 conditions: Conditions{{ 906 Type: ConditionReady, 907 Status: corev1.ConditionTrue, 908 }, { 909 Type: "Foo", 910 Status: corev1.ConditionTrue, 911 }, { 912 Type: "Bar", 913 Status: corev1.ConditionTrue, 914 }}, 915 mark: "Foo", 916 unhappy: true, 917 }} 918 doTestMarkFalseAccessor(t, cases) 919 } 920 921 type ConditionMarkUnknownTest struct { 922 name string 923 conditions Conditions 924 mark ConditionType 925 unhappy bool 926 happyIs corev1.ConditionStatus 927 } 928 929 func doTestMarkUnknownAccessor(t *testing.T, cases []ConditionMarkUnknownTest) { 930 for _, tc := range cases { 931 t.Run(tc.name, func(t *testing.T) { 932 condSet := NewLivingConditionSet(getTypes(tc.conditions)...) 933 status := &TestStatus{c: tc.conditions} 934 935 condSet.Manage(status).MarkUnknown(tc.mark, "UnitTest", "idk, just testing") 936 937 if e, a := !tc.unhappy, condSet.Manage(status).IsHappy(); e != a { 938 t.Errorf("%q expected IsHappy: %v got: %v", tc.name, e, a) 939 } 940 941 if e, a := tc.happyIs, condSet.Manage(status).GetCondition(ConditionReady).Status; e != a { 942 t.Errorf("%q expected ConditionReady: %v got: %v", tc.name, e, a) 943 } 944 945 expected := &Condition{ 946 Type: tc.mark, 947 Status: corev1.ConditionUnknown, 948 Reason: "UnitTest", 949 Message: "idk, just testing", 950 } 951 952 e, a := expected, condSet.Manage(status).GetCondition(tc.mark) 953 if diff := cmp.Diff(e, a, ignoreFields); diff != "" { 954 t.Errorf("%s (-want, +got) = %v", tc.name, diff) 955 } 956 }) 957 } 958 } 959 960 func TestMarkUnknown(t *testing.T) { 961 cases := []ConditionMarkUnknownTest{{ 962 name: "no deps", 963 mark: ConditionReady, 964 unhappy: true, 965 happyIs: corev1.ConditionUnknown, 966 }, { 967 name: "existing conditions, turns unhappy", 968 conditions: Conditions{{ 969 Type: ConditionReady, 970 Status: corev1.ConditionTrue, 971 }}, 972 mark: ConditionReady, 973 unhappy: true, 974 happyIs: corev1.ConditionUnknown, 975 }, { 976 name: "with deps, turns unhappy", 977 conditions: Conditions{{ 978 Type: ConditionReady, 979 Status: corev1.ConditionTrue, 980 }, { 981 Type: "Foo", 982 Status: corev1.ConditionTrue, 983 }}, 984 mark: ConditionReady, 985 unhappy: true, 986 happyIs: corev1.ConditionUnknown, 987 }, { 988 name: "with deps that are false, turns unhappy", 989 conditions: Conditions{{ 990 Type: ConditionReady, 991 Status: corev1.ConditionTrue, 992 }, { 993 Type: "Foo", 994 Status: corev1.ConditionFalse, 995 }, { 996 Type: "Bar", 997 Status: corev1.ConditionFalse, 998 }}, 999 mark: "Foo", 1000 unhappy: true, 1001 happyIs: corev1.ConditionFalse, 1002 }, { 1003 name: "update dep, turns unhappy", 1004 conditions: Conditions{{ 1005 Type: ConditionReady, 1006 Status: corev1.ConditionTrue, 1007 }, { 1008 Type: "Foo", 1009 Status: corev1.ConditionTrue, 1010 }}, 1011 mark: "Foo", 1012 unhappy: true, 1013 happyIs: corev1.ConditionUnknown, 1014 }, { 1015 name: "update dep, happy was unknown, turns unhappy", 1016 conditions: Conditions{{ 1017 Type: ConditionReady, 1018 Status: corev1.ConditionUnknown, 1019 }, { 1020 Type: "Foo", 1021 Status: corev1.ConditionFalse, 1022 }}, 1023 mark: "Foo", 1024 unhappy: true, 1025 happyIs: corev1.ConditionUnknown, 1026 }, { 1027 name: "update dep 1/2, turns unhappy", 1028 conditions: Conditions{{ 1029 Type: ConditionReady, 1030 Status: corev1.ConditionTrue, 1031 }, { 1032 Type: "Foo", 1033 Status: corev1.ConditionTrue, 1034 }, { 1035 Type: "Bar", 1036 Status: corev1.ConditionTrue, 1037 }}, 1038 mark: "Foo", 1039 unhappy: true, 1040 happyIs: corev1.ConditionUnknown, 1041 }} 1042 doTestMarkUnknownAccessor(t, cases) 1043 } 1044 1045 func TestInitializeConditions(t *testing.T) { 1046 condSet := NewLivingConditionSet() 1047 1048 cases := []struct { 1049 name string 1050 conditions Conditions 1051 want *Condition 1052 }{{ 1053 name: "no conditions is initialized", 1054 want: &Condition{ 1055 Type: ConditionReady, 1056 Status: corev1.ConditionUnknown, 1057 Severity: ConditionSeverityError, 1058 }, 1059 }, { 1060 name: "initialization is idempotent", 1061 conditions: Conditions{{ 1062 Type: ConditionReady, 1063 Status: corev1.ConditionUnknown, 1064 Severity: ConditionSeverityError, 1065 }}, 1066 want: &Condition{ 1067 Type: ConditionReady, 1068 Status: corev1.ConditionUnknown, 1069 Severity: ConditionSeverityError, 1070 }, 1071 // TODO(#357): Uncomment once fixed. 1072 // }, { 1073 // name: "initialization overwrites existing", 1074 // conditions: Conditions{{ 1075 // Type: ConditionReady, 1076 // Status: corev1.ConditionFalse, 1077 // Severity: ConditionSeverityError, 1078 // }}, 1079 // want: &Condition{ 1080 // Type: ConditionReady, 1081 // Status: corev1.ConditionUnknown, 1082 // Severity: ConditionSeverityError, 1083 // }, 1084 }} 1085 1086 for _, tc := range cases { 1087 t.Run(tc.name, func(t *testing.T) { 1088 status := &TestStatus{c: tc.conditions} 1089 condSet.Manage(status).InitializeConditions() 1090 if e, a := tc.want, condSet.Manage(status).GetCondition(ConditionReady); !equality.Semantic.DeepEqual(e, a) { 1091 t.Errorf("accessor, %q expected: %v got: %v", tc.name, e, a) 1092 } 1093 }) 1094 } 1095 } 1096 1097 func TestTerminalInitialization(t *testing.T) { 1098 set := NewLivingConditionSet("Foo") 1099 status := &TestStatus{} 1100 1101 manager := set.Manage(status) 1102 manager.InitializeConditions() 1103 1104 if got, want := len(status.c), 2; got != want { 1105 t.Errorf("InitializeConditions() = %v, wanted %v", got, want) 1106 } 1107 1108 manager.MarkTrue("Foo") 1109 if !manager.IsHappy() { 1110 t.Error("IsHappy() = false, wanted true") 1111 } 1112 1113 // Add a new condition "Bar" to simulate the addition of conditions. 1114 set = NewLivingConditionSet("Foo", "Bar") 1115 1116 // Create a new manager for the new set and re-initialize to simulate 1117 // Reconcile() with the new conditions. 1118 manager = set.Manage(status) 1119 manager.InitializeConditions() 1120 1121 if got, want := len(status.c), 3; got != want { 1122 t.Errorf("InitializeConditions() = %v, wanted %v", got, want) 1123 } 1124 1125 if c := manager.GetCondition("Bar"); c == nil { 1126 t.Error("GetCondition(Bar) = nil, wanted True") 1127 } else if got, want := c.Status, corev1.ConditionTrue; got != want { 1128 t.Errorf("GetCondition(Bar) = %s, wanted %s", got, want) 1129 } 1130 } 1131 1132 func TestRemoveNonTerminalConditions(t *testing.T) { 1133 set := NewLivingConditionSet("Foo") 1134 status := &TestStatus{} 1135 1136 manager := set.Manage(status) 1137 manager.MarkTrue("Foo") 1138 manager.MarkTrue("Bar") 1139 1140 if got, want := len(status.c), 3; got != want { 1141 t.Errorf("Marking true() = %v, wanted %v", got, want) 1142 } 1143 1144 if !manager.IsHappy() { 1145 t.Error("IsHappy() = false, wanted true") 1146 } 1147 1148 err := manager.ClearCondition("Bar") 1149 if err != nil { 1150 t.Error("Clear condition should not return err", err) 1151 } 1152 if got, want := len(status.c), 2; got != want { 1153 t.Errorf("Marking true() = %v, wanted %v", got, want) 1154 } 1155 1156 if !manager.IsHappy() { 1157 t.Error("IsHappy() = false, wanted true") 1158 } 1159 } 1160 1161 func TestClearConditionWithNilManager(t *testing.T) { 1162 set := NewLivingConditionSet("Foo") 1163 manager := set.Manage(nil) 1164 1165 err := manager.ClearCondition("Bar") 1166 if err != nil { 1167 t.Error("ClearCondition() expected to return nil if status is nil, got", err) 1168 } 1169 }