knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/configmap/store_test.go (about) 1 /* 2 Copyright 2018 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 configmap 18 19 import ( 20 "errors" 21 "os" 22 "os/exec" 23 "testing" 24 "time" 25 26 "github.com/google/go-cmp/cmp" 27 "github.com/google/go-cmp/cmp/cmpopts" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 31 . "knative.dev/pkg/logging/testing" 32 ) 33 34 const ( 35 config1 = "config-name-1" 36 config2 = "config-name-2" 37 ) 38 39 var constructor = func(c *corev1.ConfigMap) (interface{}, error) { 40 return c.Name, nil 41 } 42 43 func TestStoreBadConstructors(t *testing.T) { 44 tests := []struct { 45 name string 46 constructor interface{} 47 }{{ 48 name: "not a function", 49 constructor: "i'm pretending to be a function", 50 }, { 51 name: "no function arguments", 52 constructor: func() (bool, error) { return true, nil }, 53 }, { 54 name: "single argument is not a configmap", 55 constructor: func(bool) (bool, error) { return true, nil }, 56 }, { 57 name: "single return", 58 constructor: func(*corev1.ConfigMap) error { return nil }, 59 }, { 60 name: "wrong second return", 61 constructor: func(*corev1.ConfigMap) (bool, bool) { return true, true }, 62 }} 63 64 for _, test := range tests { 65 t.Run(test.name, func(t *testing.T) { 66 defer func() { 67 if r := recover(); r == nil { 68 t.Error("expected NewUntypedStore to panic") 69 } 70 }() 71 72 NewUntypedStore("store", nil, Constructors{ 73 "test": test.constructor, 74 }) 75 }) 76 } 77 } 78 79 func TestStoreWatchConfigs(t *testing.T) { 80 store := NewUntypedStore( 81 "name", 82 TestLogger(t), 83 Constructors{ 84 config1: constructor, 85 config2: constructor, 86 }, 87 ) 88 89 watcher := &mockWatcher{} 90 store.WatchConfigs(watcher) 91 92 want := []string{ 93 config1, 94 config2, 95 } 96 97 got := watcher.watches 98 99 if diff := cmp.Diff(want, got, sortStrings); diff != "" { 100 t.Errorf("Unexpected configmap watches (-want, +got):\n%s", diff) 101 } 102 } 103 104 func TestOnAfterStore(t *testing.T) { 105 var calledFor string 106 store := NewUntypedStore( 107 "name", 108 TestLogger(t), 109 Constructors{ 110 config1: constructor, 111 config2: constructor, 112 }, 113 func(name string, value interface{}) { 114 calledFor = name 115 }, 116 ) 117 118 store.OnConfigChanged(&corev1.ConfigMap{ 119 ObjectMeta: metav1.ObjectMeta{ 120 Name: config1, 121 }, 122 }) 123 124 if calledFor != config1 { 125 t.Fatalf("calledFor = %s, want %s", calledFor, config1) 126 } 127 128 store.OnConfigChanged(&corev1.ConfigMap{ 129 ObjectMeta: metav1.ObjectMeta{ 130 Name: config2, 131 }, 132 }) 133 134 if calledFor != config2 { 135 t.Fatalf("calledFor = %s, want %s", calledFor, config2) 136 } 137 } 138 139 func TestStoreConfigChange(t *testing.T) { 140 store := NewUntypedStore( 141 "name", 142 TestLogger(t), 143 Constructors{ 144 config1: constructor, 145 config2: constructor, 146 }, 147 ) 148 149 store.OnConfigChanged(&corev1.ConfigMap{ 150 ObjectMeta: metav1.ObjectMeta{ 151 Name: config1, 152 }, 153 }) 154 155 result := store.UntypedLoad(config1) 156 157 if diff := cmp.Diff(result, config1); diff != "" { 158 t.Error("Expected loaded value diff:", diff) 159 } 160 161 result = store.UntypedLoad(config2) 162 163 if diff := cmp.Diff(result, nil); diff != "" { 164 t.Error("Unexpected loaded value diff:", diff) 165 } 166 167 store.OnConfigChanged(&corev1.ConfigMap{ 168 ObjectMeta: metav1.ObjectMeta{ 169 Name: config2, 170 }, 171 }) 172 173 result = store.UntypedLoad(config2) 174 175 if diff := cmp.Diff(result, config2); diff != "" { 176 t.Error("Expected loaded value diff:", diff) 177 } 178 } 179 180 func TestStoreFailedFirstConversionCrashes(t *testing.T) { 181 if os.Getenv("CRASH") == "1" { 182 constructor := func(c *corev1.ConfigMap) (interface{}, error) { 183 return nil, errors.New("failure") 184 } 185 186 store := NewUntypedStore("name", TestLogger(t), 187 Constructors{config1: constructor}, 188 ) 189 190 store.OnConfigChanged(&corev1.ConfigMap{ 191 ObjectMeta: metav1.ObjectMeta{ 192 Name: config1, 193 }, 194 }) 195 return 196 } 197 198 cmd := exec.Command(os.Args[0], "-test.run="+t.Name()) 199 cmd.Env = append(os.Environ(), "CRASH=1") 200 err := cmd.Run() 201 202 var errExit *exec.ExitError 203 if errors.As(err, &errExit) && !errExit.Success() { 204 return 205 } 206 t.Fatal("process should have exited with status 1 - err", err) 207 } 208 209 func TestStoreFailedUpdate(t *testing.T) { 210 induceConstructorFailure := false 211 212 constructor := func(c *corev1.ConfigMap) (interface{}, error) { 213 if induceConstructorFailure { 214 return nil, errors.New("failure") 215 } 216 217 return time.Now().String(), nil 218 } 219 220 store := NewUntypedStore("name", TestLogger(t), 221 Constructors{config1: constructor}, 222 ) 223 224 store.OnConfigChanged(&corev1.ConfigMap{ 225 ObjectMeta: metav1.ObjectMeta{ 226 Name: config1, 227 }, 228 }) 229 230 firstLoad := store.UntypedLoad(config1) 231 232 induceConstructorFailure = true 233 store.OnConfigChanged(&corev1.ConfigMap{ 234 ObjectMeta: metav1.ObjectMeta{ 235 Name: config1, 236 }, 237 }) 238 239 secondLoad := store.UntypedLoad(config1) 240 241 if diff := cmp.Diff(firstLoad, secondLoad); diff != "" { 242 t.Error("Expected loaded value to remain the same dff:", diff) 243 } 244 } 245 246 type mockWatcher struct { 247 watches []string 248 } 249 250 func (w *mockWatcher) Watch(config string, o ...Observer) { 251 w.watches = append(w.watches, config) 252 } 253 254 func (*mockWatcher) Start(<-chan struct{}) error { return nil } 255 256 var _ Watcher = (*mockWatcher)(nil) 257 258 var sortStrings = cmpopts.SortSlices(func(x, y string) bool { 259 return x < y 260 })