/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package generators import ( "reflect" "strings" "testing" "k8s.io/gengo/examples/set-gen/sets" "k8s.io/gengo/types" ) func TestSingleTagExtension(t *testing.T) { // Comments only contain one tag extension and one value. var tests = []struct { comments []string extensionTag string extensionName string extensionValues []string }{ { comments: []string{"+patchMergeKey=name"}, extensionTag: "patchMergeKey", extensionName: "x-kubernetes-patch-merge-key", extensionValues: []string{"name"}, }, { comments: []string{"+patchStrategy=merge"}, extensionTag: "patchStrategy", extensionName: "x-kubernetes-patch-strategy", extensionValues: []string{"merge"}, }, { comments: []string{"+listType=atomic"}, extensionTag: "listType", extensionName: "x-kubernetes-list-type", extensionValues: []string{"atomic"}, }, { comments: []string{"+listMapKey=port"}, extensionTag: "listMapKey", extensionName: "x-kubernetes-list-map-keys", extensionValues: []string{"port"}, }, { comments: []string{"+k8s:openapi-gen=x-kubernetes-member-tag:member_test"}, extensionTag: "k8s:openapi-gen", extensionName: "x-kubernetes-member-tag", extensionValues: []string{"member_test"}, }, { comments: []string{"+k8s:openapi-gen=x-kubernetes-member-tag:member_test:member_test2"}, extensionTag: "k8s:openapi-gen", extensionName: "x-kubernetes-member-tag", extensionValues: []string{"member_test:member_test2"}, }, { // Test that poorly formatted extensions aren't added. comments: []string{ "+k8s:openapi-gen=x-kubernetes-no-value", "+k8s:openapi-gen=x-kubernetes-member-success:success", "+k8s:openapi-gen=x-kubernetes-wrong-separator;error", }, extensionTag: "k8s:openapi-gen", extensionName: "x-kubernetes-member-success", extensionValues: []string{"success"}, }, } for _, test := range tests { extensions, _ := parseExtensions(test.comments) actual := extensions[0] if actual.idlTag != test.extensionTag { t.Errorf("Extension Tag: expected (%s), actual (%s)\n", test.extensionTag, actual.idlTag) } if actual.xName != test.extensionName { t.Errorf("Extension Name: expected (%s), actual (%s)\n", test.extensionName, actual.xName) } if !reflect.DeepEqual(actual.values, test.extensionValues) { t.Errorf("Extension Values: expected (%s), actual (%s)\n", test.extensionValues, actual.values) } if actual.hasMultipleValues() { t.Errorf("%s: hasMultipleValues() should be false\n", actual.xName) } } } func TestMultipleTagExtensions(t *testing.T) { var tests = []struct { comments []string extensionTag string extensionName string extensionValues []string }{ { comments: []string{ "+listMapKey=port", "+listMapKey=protocol", }, extensionTag: "listMapKey", extensionName: "x-kubernetes-list-map-keys", extensionValues: []string{"port", "protocol"}, }, } for _, test := range tests { extensions, errors := parseExtensions(test.comments) if len(errors) > 0 { t.Errorf("Unexpected errors: %v\n", errors) } actual := extensions[0] if actual.idlTag != test.extensionTag { t.Errorf("Extension Tag: expected (%s), actual (%s)\n", test.extensionTag, actual.idlTag) } if actual.xName != test.extensionName { t.Errorf("Extension Name: expected (%s), actual (%s)\n", test.extensionName, actual.xName) } if !reflect.DeepEqual(actual.values, test.extensionValues) { t.Errorf("Extension Values: expected (%s), actual (%s)\n", test.extensionValues, actual.values) } if !actual.hasMultipleValues() { t.Errorf("%s: hasMultipleValues() should be true\n", actual.xName) } } } func TestExtensionParseErrors(t *testing.T) { var tests = []struct { comments []string errorMessage string }{ { // Missing extension value should be an error. comments: []string{ "+k8s:openapi-gen=x-kubernetes-no-value", }, errorMessage: "x-kubernetes-no-value", }, { // Wrong separator should be an error. comments: []string{ "+k8s:openapi-gen=x-kubernetes-wrong-separator;error", }, errorMessage: "x-kubernetes-wrong-separator;error", }, } for _, test := range tests { _, errors := parseExtensions(test.comments) if len(errors) == 0 { t.Errorf("Expected errors while parsing: %v\n", test.comments) } error := errors[0] if !strings.Contains(error.Error(), test.errorMessage) { t.Errorf("Error (%v) should contain substring (%s)\n", error, test.errorMessage) } } } func TestExtensionAllowedValues(t *testing.T) { var methodTests = []struct { e extension allowedValues sets.String }{ { e: extension{ idlTag: "patchStrategy", }, allowedValues: sets.NewString("merge", "retainKeys"), }, { e: extension{ idlTag: "patchMergeKey", }, allowedValues: nil, }, { e: extension{ idlTag: "listType", }, allowedValues: sets.NewString("atomic", "set", "map"), }, { e: extension{ idlTag: "listMapKey", }, allowedValues: nil, }, { e: extension{ idlTag: "k8s:openapi-gen", }, allowedValues: nil, }, } for _, test := range methodTests { if test.allowedValues != nil { if !test.e.hasAllowedValues() { t.Errorf("hasAllowedValues() expected (true), but received: false") } if !reflect.DeepEqual(test.allowedValues, test.e.allowedValues()) { t.Errorf("allowedValues() expected (%v), but received: %v", test.allowedValues, test.e.allowedValues()) } } if test.allowedValues == nil && test.e.hasAllowedValues() { t.Errorf("hasAllowedValues() expected (false), but received: true") } } var successTests = []struct { e extension }{ { e: extension{ idlTag: "patchStrategy", xName: "x-kubernetes-patch-strategy", values: []string{"merge"}, }, }, { // Validate multiple values. e: extension{ idlTag: "patchStrategy", xName: "x-kubernetes-patch-strategy", values: []string{"merge", "retainKeys"}, }, }, { e: extension{ idlTag: "patchMergeKey", xName: "x-kubernetes-patch-merge-key", values: []string{"key1"}, }, }, { e: extension{ idlTag: "listType", xName: "x-kubernetes-list-type", values: []string{"atomic"}, }, }, } for _, test := range successTests { actualErr := test.e.validateAllowedValues() if actualErr != nil { t.Errorf("Expected no error for (%v), but received: %v\n", test.e, actualErr) } } var failureTests = []struct { e extension }{ { // Every value must be allowed. e: extension{ idlTag: "patchStrategy", xName: "x-kubernetes-patch-strategy", values: []string{"disallowed", "merge"}, }, }, { e: extension{ idlTag: "patchStrategy", xName: "x-kubernetes-patch-strategy", values: []string{"foo"}, }, }, { e: extension{ idlTag: "listType", xName: "x-kubernetes-list-type", values: []string{"not-allowed"}, }, }, } for _, test := range failureTests { actualErr := test.e.validateAllowedValues() if actualErr == nil { t.Errorf("Expected error, but received none: %v\n", test.e) } } } func TestExtensionKind(t *testing.T) { var methodTests = []struct { e extension kind types.Kind }{ { e: extension{ idlTag: "patchStrategy", }, kind: types.Slice, }, { e: extension{ idlTag: "patchMergeKey", }, kind: types.Slice, }, { e: extension{ idlTag: "listType", }, kind: types.Slice, }, { e: extension{ idlTag: "listMapKey", }, kind: types.Slice, }, { e: extension{ idlTag: "k8s:openapi-gen", }, kind: "", }, } for _, test := range methodTests { if len(test.kind) > 0 { if !test.e.hasKind() { t.Errorf("%v: hasKind() expected (true), but received: false", test.e) } if test.kind != test.e.kind() { t.Errorf("%v: kind() expected (%v), but received: %v", test.e, test.kind, test.e.kind()) } } else { if test.e.hasKind() { t.Errorf("%v: hasKind() expected (false), but received: true", test.e) } } } } func TestValidateMemberExtensions(t *testing.T) { patchStrategyExtension := extension{ idlTag: "patchStrategy", xName: "x-kubernetes-patch-strategy", values: []string{"merge"}, } patchMergeKeyExtension := extension{ idlTag: "patchMergeKey", xName: "x-kubernetes-patch-merge-key", values: []string{"key1", "key2"}, } listTypeExtension := extension{ idlTag: "listType", xName: "x-kubernetes-list-type", values: []string{"atomic"}, } listMapKeysExtension := extension{ idlTag: "listMapKey", xName: "x-kubernetes-map-keys", values: []string{"key1"}, } genExtension := extension{ idlTag: "k8s:openapi-gen", xName: "x-kubernetes-member-type", values: []string{"value1"}, } sliceField := types.Member{ Name: "Containers", Type: &types.Type{ Kind: types.Slice, }, } mapField := types.Member{ Name: "Containers", Type: &types.Type{ Kind: types.Map, }, } var successTests = []struct { extensions []extension member types.Member }{ // Test single member extension { extensions: []extension{patchStrategyExtension}, member: sliceField, }, // Test multiple member extensions { extensions: []extension{ patchMergeKeyExtension, listTypeExtension, listMapKeysExtension, genExtension, // Should not generate errors during type validation }, member: sliceField, }, } for _, test := range successTests { errors := validateMemberExtensions(test.extensions, &test.member) if len(errors) > 0 { t.Errorf("validateMemberExtensions: %v should have produced no errors. Errors: %v", test.extensions, errors) } } var failureTests = []struct { extensions []extension member types.Member }{ // Test single member extension { extensions: []extension{patchStrategyExtension}, member: mapField, }, // Test multiple member extensions { extensions: []extension{ patchMergeKeyExtension, listTypeExtension, listMapKeysExtension, }, member: mapField, }, } for _, test := range failureTests { errors := validateMemberExtensions(test.extensions, &test.member) if len(errors) != len(test.extensions) { t.Errorf("validateMemberExtensions: %v should have produced all errors. Errors: %v", test.extensions, errors) } } }