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  }