k8s.io/kubernetes@v1.29.3/pkg/api/testing/compat/compatibility_tester.go (about) 1 /* 2 Copyright 2015 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 compat 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "reflect" 23 "regexp" 24 "strconv" 25 "strings" 26 "testing" 27 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/runtime/schema" 30 "k8s.io/apimachinery/pkg/util/validation/field" 31 "k8s.io/kubernetes/pkg/api/legacyscheme" 32 ) 33 34 // TestCompatibility reencodes the input using the codec for the given 35 // version and checks for the presence of the expected keys and absent 36 // keys in the resulting JSON. 37 // Based on: https://github.com/openshift/origin/blob/master/pkg/api/compatibility_test.go 38 func TestCompatibility( 39 t *testing.T, 40 version schema.GroupVersion, 41 input []byte, 42 validator func(obj runtime.Object) field.ErrorList, 43 expectedKeys map[string]string, 44 absentKeys []string, 45 ) { 46 47 // Decode 48 codec := legacyscheme.Codecs.LegacyCodec(version) 49 obj, err := runtime.Decode(codec, input) 50 if err != nil { 51 t.Fatalf("Unexpected error: %v", err) 52 } 53 54 // Validate 55 errs := validator(obj) 56 if len(errs) != 0 { 57 t.Fatalf("Unexpected validation errors: %v", errs) 58 } 59 60 // Encode 61 output, err := runtime.Encode(codec, obj) 62 if err != nil { 63 t.Fatalf("Unexpected error: %v", err) 64 } 65 66 // Validate old and new fields are encoded 67 generic := map[string]interface{}{} 68 if err := json.Unmarshal(output, &generic); err != nil { 69 t.Fatalf("Unexpected error: %v", err) 70 } 71 72 for k, expectedValue := range expectedKeys { 73 keys := strings.Split(k, ".") 74 if actualValue, ok, err := getJSONValue(generic, keys...); err != nil || !ok { 75 t.Errorf("Unexpected error for %s: %v", k, err) 76 } else if !reflect.DeepEqual(expectedValue, fmt.Sprintf("%v", actualValue)) { 77 t.Errorf("Unexpected value for %v: expected %v, got %v", k, expectedValue, actualValue) 78 } 79 } 80 81 for _, absentKey := range absentKeys { 82 keys := strings.Split(absentKey, ".") 83 actualValue, ok, err := getJSONValue(generic, keys...) 84 if err == nil || ok { 85 t.Errorf("Unexpected value found for key %s: %v", absentKey, actualValue) 86 } 87 } 88 89 if t.Failed() { 90 data, err := json.MarshalIndent(obj, "", " ") 91 if err != nil { 92 t.Log(err) 93 } else { 94 t.Log(string(data)) 95 } 96 t.Logf("2: Encoded value: %v", string(output)) 97 } 98 } 99 100 func getJSONValue(data map[string]interface{}, keys ...string) (interface{}, bool, error) { 101 // No keys, current value is it 102 if len(keys) == 0 { 103 return data, true, nil 104 } 105 106 // Get the key (and optional index) 107 key := keys[0] 108 index := -1 109 if matches := regexp.MustCompile(`^(.*)\[(\d+)\]$`).FindStringSubmatch(key); len(matches) > 0 { 110 key = matches[1] 111 index, _ = strconv.Atoi(matches[2]) 112 } 113 114 // Look up the value 115 value, ok := data[key] 116 if !ok { 117 return nil, false, fmt.Errorf("no key %s found", key) 118 } 119 120 // Get the indexed value if an index is specified 121 if index >= 0 { 122 valueSlice, ok := value.([]interface{}) 123 if !ok { 124 return nil, false, fmt.Errorf("key %s did not hold a slice", key) 125 } 126 if index >= len(valueSlice) { 127 return nil, false, fmt.Errorf("index %d out of bounds for slice at key: %v", index, key) 128 } 129 value = valueSlice[index] 130 } 131 132 if len(keys) == 1 { 133 return value, true, nil 134 } 135 136 childData, ok := value.(map[string]interface{}) 137 if !ok { 138 return nil, false, fmt.Errorf("key %s did not hold a map", keys[0]) 139 } 140 return getJSONValue(childData, keys[1:]...) 141 }