knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/duck/verify_test.go (about) 1 /* 2 Copyright 2017 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 duck 18 19 import ( 20 "errors" 21 "testing" 22 23 "k8s.io/apimachinery/pkg/runtime" 24 "k8s.io/apimachinery/pkg/runtime/schema" 25 ) 26 27 func TestMatches(t *testing.T) { 28 tests := []struct { 29 name string 30 instance interface{} 31 iface Implementable 32 }{{ 33 name: "foo matches fooable", 34 instance: &Foo{}, 35 iface: &Fooable{}, 36 }, { 37 name: "bar matches barable", 38 instance: &Bar{}, 39 iface: &Barable{}, 40 }, { 41 name: "slice matches sliceable", 42 instance: &Slice{}, 43 iface: &Sliceable{}, 44 }, { 45 name: "string matches stringable", 46 instance: &String{}, 47 iface: &emptyStringable, 48 }, { 49 name: "other matches foo", 50 instance: &struct { 51 Status struct { 52 Fooable *Fooable `json:"fooable,omitempty"` 53 } `json:"status,omitempty"` 54 }{}, 55 iface: &Fooable{}, 56 }, { 57 name: "other (all) matches fooable", 58 instance: &struct { 59 Status struct { 60 Fooable *Fooable `json:"fooable,omitempty"` 61 Barable *Barable `json:"barable,omitempty"` 62 Sliceable Sliceable `json:"sliceable,omitempty"` 63 Stringable *Stringable `json:"stringable,omitempty"` 64 } `json:"status,omitempty"` 65 }{}, 66 iface: &Fooable{}, 67 }, { 68 name: "other (all) matches barable", 69 instance: &struct { 70 Status struct { 71 Fooable *Fooable `json:"fooable,omitempty"` 72 Barable *Barable `json:"barable,omitempty"` 73 Sliceable Sliceable `json:"sliceable,omitempty"` 74 Stringable *Stringable `json:"stringable,omitempty"` 75 } `json:"status,omitempty"` 76 }{}, 77 iface: &Barable{}, 78 }, { 79 name: "other (all) matches sliceable", 80 instance: &struct { 81 Status struct { 82 Fooable *Fooable `json:"fooable,omitempty"` 83 Barable *Barable `json:"barable,omitempty"` 84 Sliceable Sliceable `json:"sliceable,omitempty"` 85 Stringable *Stringable `json:"stringable,omitempty"` 86 } `json:"status,omitempty"` 87 }{}, 88 iface: &Sliceable{}, 89 }, { 90 name: "other (all) matches stringable", 91 instance: &struct { 92 Status struct { 93 Fooable *Fooable `json:"fooable,omitempty"` 94 Barable *Barable `json:"barable,omitempty"` 95 Sliceable Sliceable `json:"sliceable,omitempty"` 96 Stringable *Stringable `json:"stringable,omitempty"` 97 } `json:"status,omitempty"` 98 }{}, 99 iface: &emptyStringable, 100 }} 101 102 for _, test := range tests { 103 t.Run(test.name, func(t *testing.T) { 104 if err := VerifyType(test.instance, test.iface); err != nil { 105 t.Error(err) 106 } 107 108 ok, err := ConformsToType(test.instance, test.iface) 109 if err != nil { 110 t.Error(err) 111 } 112 113 if !ok { 114 t.Errorf("Expected %T to conform to %T", test.instance, test.iface) 115 } 116 }) 117 } 118 } 119 120 func TestMismatches(t *testing.T) { 121 tests := []struct { 122 name string 123 instance interface{} 124 iface Implementable 125 }{{ 126 name: "foo doesn't match barable", 127 instance: &Foo{}, 128 iface: &Barable{}, 129 }, { 130 name: "bar doesn't match fooable", 131 instance: &Bar{}, 132 iface: &Fooable{}, 133 }, { 134 name: "foo doesn't match sliceable", 135 instance: &Foo{}, 136 iface: &Sliceable{}, 137 }, { 138 name: "other matches neither (foo)", 139 instance: &struct { 140 Status struct { 141 Done bool `json:"done,omitempty"` 142 } `json:"status,omitempty"` 143 }{}, 144 iface: &Fooable{}, 145 }, { 146 name: "other matches neither (slice)", 147 instance: &struct { 148 Status struct { 149 Done bool `json:"done,omitempty"` 150 } `json:"status,omitempty"` 151 }{}, 152 iface: &Sliceable{}, 153 }} 154 155 for _, test := range tests { 156 t.Run(test.name, func(t *testing.T) { 157 if err := VerifyType(test.instance, test.iface); err == nil { 158 t.Errorf("Unexpected success %T implements %T", test.instance, test.iface) 159 } 160 161 ok, err := ConformsToType(test.instance, test.iface) 162 if err != nil { 163 t.Error(err) 164 } 165 166 if ok { 167 t.Errorf("Expected %T to not conform to %T", test.instance, test.iface) 168 } 169 }) 170 } 171 } 172 173 func TestErrors(t *testing.T) { 174 tests := []struct { 175 name string 176 instance interface{} 177 iface Implementable 178 }{{ 179 name: "duck type - fails to marshal", 180 instance: &Foo{}, 181 iface: &UnableToMarshal{}, 182 }, { 183 name: "duck type - fails to unmarshal", 184 instance: &Foo{}, 185 iface: &UnableToUnmarshal{}, 186 }, { 187 name: "instance - fails to unmarshal", 188 instance: &UnableToUnmarshal{}, 189 iface: &Fooable{}, 190 }, { 191 name: "instance - fails to marshal", 192 instance: &UnableToMarshal{}, 193 iface: &Fooable{}, 194 }, { 195 name: "duck type - unexported fields", 196 instance: &Foo{}, 197 iface: &UnexportedFields{}, 198 }} 199 200 for _, test := range tests { 201 t.Run(test.name, func(t *testing.T) { 202 if err := VerifyType(test.instance, test.iface); err == nil { 203 t.Error("expected VerifyType to return an error") 204 } 205 206 if _, err := ConformsToType(test.instance, test.iface); err == nil { 207 t.Error("expected ConformsToType to return an error") 208 } 209 }) 210 } 211 } 212 213 // Define a "Fooable" duck type. 214 type Fooable struct { 215 Field1 string `json:"field1,omitempty"` 216 Field2 string `json:"field2,omitempty"` 217 } 218 type Foo struct { 219 Status FooStatus `json:"status"` 220 } 221 type FooStatus struct { 222 Fooable *Fooable `json:"fooable,omitempty"` 223 } 224 225 var ( 226 _ Implementable = (*Fooable)(nil) 227 _ Populatable = (*Foo)(nil) 228 ) 229 230 func (*Foo) GetObjectKind() schema.ObjectKind { 231 return nil // not used 232 } 233 234 func (*Foo) DeepCopyObject() runtime.Object { 235 return nil // not used 236 } 237 238 func (*Foo) GetListType() runtime.Object { 239 return nil // not used 240 } 241 242 func (*Fooable) GetFullType() Populatable { 243 return &Foo{} 244 } 245 246 func (f *Foo) Populate() { 247 f.Status.Fooable = &Fooable{ 248 // Populate ALL fields 249 Field1: "foo", 250 Field2: "bar", 251 } 252 } 253 254 // Define a "Barable" duck type. 255 type Barable struct { 256 Field1 int `json:"field1,omitempty"` 257 Field2 bool `json:"field2,omitempty"` 258 } 259 type Bar struct { 260 Status BarStatus `json:"status"` 261 } 262 type BarStatus struct { 263 Barable *Barable `json:"barable,omitempty"` 264 } 265 266 var ( 267 _ Implementable = (*Barable)(nil) 268 _ Populatable = (*Bar)(nil) 269 ) 270 271 func (*Bar) GetObjectKind() schema.ObjectKind { 272 return nil // not used 273 } 274 275 func (*Bar) DeepCopyObject() runtime.Object { 276 return nil // not used 277 } 278 279 func (*Bar) GetListType() runtime.Object { 280 return nil // not used 281 } 282 283 func (*Barable) GetFullType() Populatable { 284 return &Bar{} 285 } 286 287 func (f *Bar) Populate() { 288 f.Status.Barable = &Barable{ 289 // Populate ALL fields 290 Field1: 42, 291 Field2: true, 292 } 293 } 294 295 // Define a "Sliceable" duck type. 296 type AStruct struct { 297 Field string `json:"field,omitempty"` 298 } 299 type ( 300 Sliceable []AStruct 301 Slice struct { 302 Status SliceStatus `json:"status"` 303 } 304 ) 305 306 type SliceStatus struct { 307 Sliceable *Sliceable `json:"sliceable,omitempty"` 308 } 309 310 var ( 311 _ Implementable = (*Sliceable)(nil) 312 _ Populatable = (*Slice)(nil) 313 ) 314 315 func (*Slice) GetObjectKind() schema.ObjectKind { 316 return nil // not used 317 } 318 319 func (*Slice) DeepCopyObject() runtime.Object { 320 return nil // not used 321 } 322 323 func (*Slice) GetListType() runtime.Object { 324 return nil // not used 325 } 326 327 func (*Sliceable) GetFullType() Populatable { 328 return &Slice{} 329 } 330 331 func (f *Slice) Populate() { 332 f.Status.Sliceable = &Sliceable{{"foo"}, {"bar"}} 333 } 334 335 // Define a "Stringable" duck type. 336 type ( 337 Stringable string 338 String struct { 339 Status StringStatus `json:"status"` 340 } 341 ) 342 343 type StringStatus struct { 344 Stringable Stringable `json:"stringable,omitempty"` 345 } 346 347 var ( 348 _ Implementable = (*Stringable)(nil) 349 _ Populatable = (*String)(nil) 350 ) 351 352 func (*String) GetObjectKind() schema.ObjectKind { 353 return nil // not used 354 } 355 356 func (*String) DeepCopyObject() runtime.Object { 357 return nil // not used 358 } 359 360 func (*String) GetListType() runtime.Object { 361 return nil // not used 362 } 363 364 func (*Stringable) GetFullType() Populatable { 365 return &String{} 366 } 367 368 func (f *String) Populate() { 369 f.Status.Stringable = Stringable("hello duck") 370 } 371 372 // We have to do this for Stringable because we're aliasing a value type. 373 var emptyStringable Stringable 374 375 // For testing this doubles as the 'Implementable' 376 // and 'Populatable' 377 type UnableToMarshal struct{} 378 379 var ( 380 _ Implementable = (*UnableToMarshal)(nil) 381 _ Populatable = (*UnableToMarshal)(nil) 382 ) 383 384 func (*UnableToMarshal) GetObjectKind() schema.ObjectKind { 385 return nil // not used 386 } 387 388 func (*UnableToMarshal) DeepCopyObject() runtime.Object { 389 return nil // not used 390 } 391 392 func (*UnableToMarshal) GetListType() runtime.Object { 393 return nil // not used 394 } 395 396 func (u *UnableToMarshal) GetFullType() Populatable { 397 return u 398 } 399 400 func (*UnableToMarshal) Populate() { 401 } 402 403 func (*UnableToMarshal) MarshalJSON() ([]byte, error) { 404 return nil, errors.New("I will never marshal for you") 405 } 406 407 // For testing this doubles as the 'Implementable' 408 // and 'Populatable' 409 type UnableToUnmarshal struct{} 410 411 var ( 412 _ Implementable = (*UnableToUnmarshal)(nil) 413 _ Populatable = (*UnableToUnmarshal)(nil) 414 ) 415 416 func (*UnableToUnmarshal) GetObjectKind() schema.ObjectKind { 417 return nil // not used 418 } 419 420 func (*UnableToUnmarshal) DeepCopyObject() runtime.Object { 421 return nil // not used 422 } 423 424 func (*UnableToUnmarshal) GetListType() runtime.Object { 425 return nil // not used 426 } 427 428 func (u *UnableToUnmarshal) GetFullType() Populatable { 429 return u 430 } 431 432 func (*UnableToUnmarshal) Populate() { 433 } 434 435 func (*UnableToUnmarshal) UnmarshalJSON([]byte) error { 436 return errors.New("I will never unmarshal for you") 437 } 438 439 // For testing this doubles as the 'Implementable' 440 // and 'Populatable' 441 type UnexportedFields struct { 442 a string 443 } 444 445 var ( 446 _ Implementable = (*UnexportedFields)(nil) 447 _ Populatable = (*UnexportedFields)(nil) 448 ) 449 450 func (*UnexportedFields) GetObjectKind() schema.ObjectKind { 451 return nil // not used 452 } 453 454 func (*UnexportedFields) DeepCopyObject() runtime.Object { 455 return nil // not used 456 } 457 458 func (*UnexportedFields) GetListType() runtime.Object { 459 return nil // not used 460 } 461 462 func (*UnexportedFields) GetFullType() Populatable { 463 return &UnexportedFields{} 464 } 465 466 func (u *UnexportedFields) Populate() { 467 u.a = "hello" 468 }