k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/generators/extension_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 generators 18 19 import ( 20 "reflect" 21 "strings" 22 "testing" 23 24 "k8s.io/gengo/v2/types" 25 "k8s.io/kube-openapi/pkg/util/sets" 26 ) 27 28 func TestSingleTagExtension(t *testing.T) { 29 30 // Comments only contain one tag extension and one value. 31 var tests = []struct { 32 comments []string 33 extensionTag string 34 extensionName string 35 extensionValues []string 36 }{ 37 { 38 comments: []string{"+patchMergeKey=name"}, 39 extensionTag: "patchMergeKey", 40 extensionName: "x-kubernetes-patch-merge-key", 41 extensionValues: []string{"name"}, 42 }, 43 { 44 comments: []string{"+patchStrategy=merge"}, 45 extensionTag: "patchStrategy", 46 extensionName: "x-kubernetes-patch-strategy", 47 extensionValues: []string{"merge"}, 48 }, 49 { 50 comments: []string{"+listType=atomic"}, 51 extensionTag: "listType", 52 extensionName: "x-kubernetes-list-type", 53 extensionValues: []string{"atomic"}, 54 }, 55 { 56 comments: []string{"+listMapKey=port"}, 57 extensionTag: "listMapKey", 58 extensionName: "x-kubernetes-list-map-keys", 59 extensionValues: []string{"port"}, 60 }, 61 { 62 comments: []string{"+mapType=granular"}, 63 extensionTag: "mapType", 64 extensionName: "x-kubernetes-map-type", 65 extensionValues: []string{"granular"}, 66 }, 67 { 68 comments: []string{"+k8s:openapi-gen=x-kubernetes-member-tag:member_test"}, 69 extensionTag: "k8s:openapi-gen", 70 extensionName: "x-kubernetes-member-tag", 71 extensionValues: []string{"member_test"}, 72 }, 73 { 74 comments: []string{"+k8s:openapi-gen=x-kubernetes-member-tag:member_test:member_test2"}, 75 extensionTag: "k8s:openapi-gen", 76 extensionName: "x-kubernetes-member-tag", 77 extensionValues: []string{"member_test:member_test2"}, 78 }, 79 { 80 // Test that poorly formatted extensions aren't added. 81 comments: []string{ 82 "+k8s:openapi-gen=x-kubernetes-no-value", 83 "+k8s:openapi-gen=x-kubernetes-member-success:success", 84 "+k8s:openapi-gen=x-kubernetes-wrong-separator;error", 85 }, 86 extensionTag: "k8s:openapi-gen", 87 extensionName: "x-kubernetes-member-success", 88 extensionValues: []string{"success"}, 89 }, 90 } 91 for _, test := range tests { 92 extensions, _ := parseExtensions(test.comments) 93 actual := extensions[0] 94 if actual.idlTag != test.extensionTag { 95 t.Errorf("Extension Tag: expected (%s), actual (%s)\n", test.extensionTag, actual.idlTag) 96 } 97 if actual.xName != test.extensionName { 98 t.Errorf("Extension Name: expected (%s), actual (%s)\n", test.extensionName, actual.xName) 99 } 100 if !reflect.DeepEqual(actual.values, test.extensionValues) { 101 t.Errorf("Extension Values: expected (%s), actual (%s)\n", test.extensionValues, actual.values) 102 } 103 if actual.hasMultipleValues() { 104 t.Errorf("%s: hasMultipleValues() should be false\n", actual.xName) 105 } 106 } 107 108 } 109 110 func TestMultipleTagExtensions(t *testing.T) { 111 112 var tests = []struct { 113 comments []string 114 extensionTag string 115 extensionName string 116 extensionValues []string 117 }{ 118 { 119 comments: []string{ 120 "+listMapKey=port", 121 "+listMapKey=protocol", 122 }, 123 extensionTag: "listMapKey", 124 extensionName: "x-kubernetes-list-map-keys", 125 extensionValues: []string{"port", "protocol"}, 126 }, 127 } 128 for _, test := range tests { 129 extensions, errors := parseExtensions(test.comments) 130 if len(errors) > 0 { 131 t.Errorf("Unexpected errors: %v\n", errors) 132 } 133 actual := extensions[0] 134 if actual.idlTag != test.extensionTag { 135 t.Errorf("Extension Tag: expected (%s), actual (%s)\n", test.extensionTag, actual.idlTag) 136 } 137 if actual.xName != test.extensionName { 138 t.Errorf("Extension Name: expected (%s), actual (%s)\n", test.extensionName, actual.xName) 139 } 140 if !reflect.DeepEqual(actual.values, test.extensionValues) { 141 t.Errorf("Extension Values: expected (%s), actual (%s)\n", test.extensionValues, actual.values) 142 } 143 if !actual.hasMultipleValues() { 144 t.Errorf("%s: hasMultipleValues() should be true\n", actual.xName) 145 } 146 } 147 148 } 149 150 func TestExtensionParseErrors(t *testing.T) { 151 152 var tests = []struct { 153 comments []string 154 errorMessage string 155 }{ 156 { 157 // Missing extension value should be an error. 158 comments: []string{ 159 "+k8s:openapi-gen=x-kubernetes-no-value", 160 }, 161 errorMessage: "x-kubernetes-no-value", 162 }, 163 { 164 // Wrong separator should be an error. 165 comments: []string{ 166 "+k8s:openapi-gen=x-kubernetes-wrong-separator;error", 167 }, 168 errorMessage: "x-kubernetes-wrong-separator;error", 169 }, 170 } 171 172 for _, test := range tests { 173 _, errors := parseExtensions(test.comments) 174 if len(errors) == 0 { 175 t.Errorf("Expected errors while parsing: %v\n", test.comments) 176 } 177 error := errors[0] 178 if !strings.Contains(error.Error(), test.errorMessage) { 179 t.Errorf("Error (%v) should contain substring (%s)\n", error, test.errorMessage) 180 } 181 } 182 } 183 184 func TestExtensionAllowedValues(t *testing.T) { 185 186 var methodTests = []struct { 187 e extension 188 allowedValues sets.String 189 }{ 190 { 191 e: extension{ 192 idlTag: "patchStrategy", 193 }, 194 allowedValues: sets.NewString("merge", "retainKeys"), 195 }, 196 { 197 e: extension{ 198 idlTag: "patchMergeKey", 199 }, 200 allowedValues: nil, 201 }, 202 { 203 e: extension{ 204 idlTag: "listType", 205 }, 206 allowedValues: sets.NewString("atomic", "set", "map"), 207 }, 208 { 209 e: extension{ 210 idlTag: "listMapKey", 211 }, 212 allowedValues: nil, 213 }, 214 { 215 e: extension{ 216 idlTag: "mapType", 217 }, 218 allowedValues: sets.NewString("atomic", "granular"), 219 }, 220 { 221 e: extension{ 222 idlTag: "structType", 223 }, 224 allowedValues: sets.NewString("atomic", "granular"), 225 }, 226 { 227 e: extension{ 228 idlTag: "k8s:openapi-gen", 229 }, 230 allowedValues: nil, 231 }, 232 } 233 for _, test := range methodTests { 234 if test.allowedValues != nil { 235 if !test.e.hasAllowedValues() { 236 t.Errorf("hasAllowedValues() expected (true), but received: false") 237 } 238 if !reflect.DeepEqual(test.allowedValues, test.e.allowedValues()) { 239 t.Errorf("allowedValues() expected (%v), but received: %v", 240 test.allowedValues, test.e.allowedValues()) 241 } 242 } 243 if test.allowedValues == nil && test.e.hasAllowedValues() { 244 t.Errorf("hasAllowedValues() expected (false), but received: true") 245 } 246 } 247 248 var successTests = []struct { 249 e extension 250 }{ 251 { 252 e: extension{ 253 idlTag: "patchStrategy", 254 xName: "x-kubernetes-patch-strategy", 255 values: []string{"merge"}, 256 }, 257 }, 258 { 259 // Validate multiple values. 260 e: extension{ 261 idlTag: "patchStrategy", 262 xName: "x-kubernetes-patch-strategy", 263 values: []string{"merge", "retainKeys"}, 264 }, 265 }, 266 { 267 e: extension{ 268 idlTag: "patchMergeKey", 269 xName: "x-kubernetes-patch-merge-key", 270 values: []string{"key1"}, 271 }, 272 }, 273 { 274 e: extension{ 275 idlTag: "listType", 276 xName: "x-kubernetes-list-type", 277 values: []string{"atomic"}, 278 }, 279 }, 280 { 281 e: extension{ 282 idlTag: "mapType", 283 xName: "x-kubernetes-map-type", 284 values: []string{"atomic"}, 285 }, 286 }, 287 { 288 e: extension{ 289 idlTag: "structType", 290 xName: "x-kubernetes-map-type", 291 values: []string{"granular"}, 292 }, 293 }, 294 } 295 for _, test := range successTests { 296 actualErr := test.e.validateAllowedValues() 297 if actualErr != nil { 298 t.Errorf("Expected no error for (%v), but received: %v\n", test.e, actualErr) 299 } 300 } 301 302 var failureTests = []struct { 303 e extension 304 }{ 305 { 306 // Every value must be allowed. 307 e: extension{ 308 idlTag: "patchStrategy", 309 xName: "x-kubernetes-patch-strategy", 310 values: []string{"disallowed", "merge"}, 311 }, 312 }, 313 { 314 e: extension{ 315 idlTag: "patchStrategy", 316 xName: "x-kubernetes-patch-strategy", 317 values: []string{"foo"}, 318 }, 319 }, 320 { 321 e: extension{ 322 idlTag: "listType", 323 xName: "x-kubernetes-list-type", 324 values: []string{"not-allowed"}, 325 }, 326 }, 327 { 328 e: extension{ 329 idlTag: "mapType", 330 xName: "x-kubernetes-map-type", 331 values: []string{"something-pretty-wrong"}, 332 }, 333 }, 334 { 335 e: extension{ 336 idlTag: "structType", 337 xName: "x-kubernetes-map-type", 338 values: []string{"not-quite-right"}, 339 }, 340 }, 341 } 342 for _, test := range failureTests { 343 actualErr := test.e.validateAllowedValues() 344 if actualErr == nil { 345 t.Errorf("Expected error, but received none: %v\n", test.e) 346 } 347 } 348 349 } 350 351 func TestExtensionKind(t *testing.T) { 352 353 var methodTests = []struct { 354 e extension 355 kind types.Kind 356 }{ 357 { 358 e: extension{ 359 idlTag: "patchStrategy", 360 }, 361 kind: types.Slice, 362 }, 363 { 364 e: extension{ 365 idlTag: "patchMergeKey", 366 }, 367 kind: types.Slice, 368 }, 369 { 370 e: extension{ 371 idlTag: "listType", 372 }, 373 kind: types.Slice, 374 }, 375 { 376 e: extension{ 377 idlTag: "mapType", 378 }, 379 kind: types.Map, 380 }, 381 { 382 e: extension{ 383 idlTag: "structType", 384 }, 385 kind: types.Struct, 386 }, 387 { 388 e: extension{ 389 idlTag: "listMapKey", 390 }, 391 kind: types.Slice, 392 }, 393 { 394 e: extension{ 395 idlTag: "k8s:openapi-gen", 396 }, 397 kind: "", 398 }, 399 } 400 for _, test := range methodTests { 401 if len(test.kind) > 0 { 402 if !test.e.hasKind() { 403 t.Errorf("%v: hasKind() expected (true), but received: false", test.e) 404 } 405 if test.kind != test.e.kind() { 406 t.Errorf("%v: kind() expected (%v), but received: %v", test.e, test.kind, test.e.kind()) 407 } 408 } else { 409 if test.e.hasKind() { 410 t.Errorf("%v: hasKind() expected (false), but received: true", test.e) 411 } 412 } 413 } 414 } 415 416 func TestValidateMemberExtensions(t *testing.T) { 417 418 patchStrategyExtension := extension{ 419 idlTag: "patchStrategy", 420 xName: "x-kubernetes-patch-strategy", 421 values: []string{"merge"}, 422 } 423 patchMergeKeyExtension := extension{ 424 idlTag: "patchMergeKey", 425 xName: "x-kubernetes-patch-merge-key", 426 values: []string{"key1", "key2"}, 427 } 428 listTypeExtension := extension{ 429 idlTag: "listType", 430 xName: "x-kubernetes-list-type", 431 values: []string{"atomic"}, 432 } 433 listMapKeysExtension := extension{ 434 idlTag: "listMapKey", 435 xName: "x-kubernetes-map-keys", 436 values: []string{"key1"}, 437 } 438 genExtension := extension{ 439 idlTag: "k8s:openapi-gen", 440 xName: "x-kubernetes-member-type", 441 values: []string{"value1"}, 442 } 443 444 sliceField := types.Member{ 445 Name: "Containers", 446 Type: &types.Type{ 447 Kind: types.Slice, 448 }, 449 } 450 mapField := types.Member{ 451 Name: "Containers", 452 Type: &types.Type{ 453 Kind: types.Map, 454 }, 455 } 456 457 var successTests = []struct { 458 extensions []extension 459 member types.Member 460 }{ 461 // Test single member extension 462 { 463 extensions: []extension{patchStrategyExtension}, 464 member: sliceField, 465 }, 466 // Test multiple member extensions 467 { 468 extensions: []extension{ 469 patchMergeKeyExtension, 470 listTypeExtension, 471 listMapKeysExtension, 472 genExtension, // Should not generate errors during type validation 473 }, 474 member: sliceField, 475 }, 476 } 477 for _, test := range successTests { 478 errors := validateMemberExtensions(test.extensions, &test.member) 479 if len(errors) > 0 { 480 t.Errorf("validateMemberExtensions: %v should have produced no errors. Errors: %v", 481 test.extensions, errors) 482 } 483 } 484 485 var failureTests = []struct { 486 extensions []extension 487 member types.Member 488 }{ 489 // Test single member extension 490 { 491 extensions: []extension{patchStrategyExtension}, 492 member: mapField, 493 }, 494 // Test multiple member extensions 495 { 496 extensions: []extension{ 497 patchMergeKeyExtension, 498 listTypeExtension, 499 listMapKeysExtension, 500 }, 501 member: mapField, 502 }, 503 } 504 for _, test := range failureTests { 505 errors := validateMemberExtensions(test.extensions, &test.member) 506 if len(errors) != len(test.extensions) { 507 t.Errorf("validateMemberExtensions: %v should have produced all errors. Errors: %v", 508 test.extensions, errors) 509 } 510 } 511 512 }