1
16
17 package fieldpath
18
19 import (
20 "strings"
21 "testing"
22
23 "k8s.io/api/core/v1"
24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25 )
26
27 func BenchmarkFormatMap(b *testing.B) {
28 var s string
29 m := map[string]string{
30 "spec.pod.beta.kubernetes.io/statefulset-index": "1",
31 "Www.k8s.io/test": "1",
32 "foo": "bar",
33 "flannel.alpha.coreos.com/backend-data": `{"VNI":1,"VtepMAC":"ce:f9:c7:a4:de:64"}`,
34 "flannel.alpha.coreos.com/backend-type": "vxlan",
35 "flannel.alpha.coreos.com/kube-subnet-manager": "true",
36 "flannel.alpha.coreos.com/public-ip": "192.168.19.160",
37 "management.cattle.io/pod-limits": `{"cpu":"11400m","memory":"7965Mi"}`,
38 "management.cattle.io/pod-requests": `{"cpu":"2482m","memory":"7984Mi","pods":"26"}`,
39 "node.alpha.kubernetes.io/ttl": "0",
40 "volumes.kubernetes.io/controller-managed-attach-detach": "true",
41 }
42 b.ReportAllocs()
43 b.ResetTimer()
44 for i := 0; i < b.N; i++ {
45 s = FormatMap(m)
46 }
47
48 _ = s
49 }
50
51 func TestExtractFieldPathAsString(t *testing.T) {
52 cases := []struct {
53 name string
54 fieldPath string
55 obj interface{}
56 expectedValue string
57 expectedMessageFragment string
58 }{
59 {
60 name: "not an API object",
61 fieldPath: "metadata.name",
62 obj: "",
63 expectedMessageFragment: "object does not implement the Object interfaces",
64 },
65 {
66 name: "ok - namespace",
67 fieldPath: "metadata.namespace",
68 obj: &v1.Pod{
69 ObjectMeta: metav1.ObjectMeta{
70 Namespace: "object-namespace",
71 },
72 },
73 expectedValue: "object-namespace",
74 },
75 {
76 name: "ok - name",
77 fieldPath: "metadata.name",
78 obj: &v1.Pod{
79 ObjectMeta: metav1.ObjectMeta{
80 Name: "object-name",
81 },
82 },
83 expectedValue: "object-name",
84 },
85 {
86 name: "ok - labels",
87 fieldPath: "metadata.labels",
88 obj: &v1.Pod{
89 ObjectMeta: metav1.ObjectMeta{
90 Labels: map[string]string{"key": "value"},
91 },
92 },
93 expectedValue: "key=\"value\"",
94 },
95 {
96 name: "ok - labels bslash n",
97 fieldPath: "metadata.labels",
98 obj: &v1.Pod{
99 ObjectMeta: metav1.ObjectMeta{
100 Labels: map[string]string{"key": "value\n"},
101 },
102 },
103 expectedValue: "key=\"value\\n\"",
104 },
105 {
106 name: "ok - annotations",
107 fieldPath: "metadata.annotations",
108 obj: &v1.Pod{
109 ObjectMeta: metav1.ObjectMeta{
110 Annotations: map[string]string{"builder": "john-doe"},
111 },
112 },
113 expectedValue: "builder=\"john-doe\"",
114 },
115 {
116 name: "ok - annotation",
117 fieldPath: "metadata.annotations['spec.pod.beta.kubernetes.io/statefulset-index']",
118 obj: &v1.Pod{
119 ObjectMeta: metav1.ObjectMeta{
120 Annotations: map[string]string{"spec.pod.beta.kubernetes.io/statefulset-index": "1"},
121 },
122 },
123 expectedValue: "1",
124 },
125 {
126 name: "ok - annotation",
127 fieldPath: "metadata.annotations['Www.k8s.io/test']",
128 obj: &v1.Pod{
129 ObjectMeta: metav1.ObjectMeta{
130 Annotations: map[string]string{"Www.k8s.io/test": "1"},
131 },
132 },
133 expectedValue: "1",
134 },
135 {
136 name: "ok - uid",
137 fieldPath: "metadata.uid",
138 obj: &v1.Pod{
139 ObjectMeta: metav1.ObjectMeta{
140 UID: "b70b3269-858e-12a8-9cf2-1232a194038a",
141 },
142 },
143 expectedValue: "b70b3269-858e-12a8-9cf2-1232a194038a",
144 },
145 {
146 name: "ok - label",
147 fieldPath: "metadata.labels['something']",
148 obj: &v1.Pod{
149 ObjectMeta: metav1.ObjectMeta{
150 Labels: map[string]string{
151 "something": "label value",
152 },
153 },
154 },
155 expectedValue: "label value",
156 },
157 {
158 name: "invalid expression",
159 fieldPath: "metadata.whoops",
160 obj: &v1.Pod{
161 ObjectMeta: metav1.ObjectMeta{
162 Namespace: "object-namespace",
163 },
164 },
165 expectedMessageFragment: "unsupported fieldPath",
166 },
167 {
168 name: "invalid annotation key",
169 fieldPath: "metadata.annotations['invalid~key']",
170 obj: &v1.Pod{
171 ObjectMeta: metav1.ObjectMeta{
172 Annotations: map[string]string{"foo": "bar"},
173 },
174 },
175 expectedMessageFragment: "invalid key subscript in metadata.annotations",
176 },
177 {
178 name: "invalid label key",
179 fieldPath: "metadata.labels['Www.k8s.io/test']",
180 obj: &v1.Pod{
181 ObjectMeta: metav1.ObjectMeta{
182 Annotations: map[string]string{"foo": "bar"},
183 },
184 },
185 expectedMessageFragment: "invalid key subscript in metadata.labels",
186 },
187 {
188 name: "invalid subscript",
189 fieldPath: "metadata.notexisting['something']",
190 obj: &v1.Pod{},
191 expectedMessageFragment: "fieldPath \"metadata.notexisting['something']\" does not support subscript",
192 },
193 }
194
195 for _, tc := range cases {
196 actual, err := ExtractFieldPathAsString(tc.obj, tc.fieldPath)
197 if err != nil {
198 if tc.expectedMessageFragment != "" {
199 if !strings.Contains(err.Error(), tc.expectedMessageFragment) {
200 t.Errorf("%v: unexpected error message: %q, expected to contain %q", tc.name, err, tc.expectedMessageFragment)
201 }
202 } else {
203 t.Errorf("%v: unexpected error: %v", tc.name, err)
204 }
205 } else if tc.expectedMessageFragment != "" {
206 t.Errorf("%v: expected error: %v", tc.name, tc.expectedMessageFragment)
207 } else if e := tc.expectedValue; e != "" && e != actual {
208 t.Errorf("%v: unexpected result; got %q, expected %q", tc.name, actual, e)
209 }
210 }
211 }
212
213 func TestSplitMaybeSubscriptedPath(t *testing.T) {
214 cases := []struct {
215 fieldPath string
216 expectedPath string
217 expectedSubscript string
218 expectedOK bool
219 }{
220 {
221 fieldPath: "metadata.annotations['key']",
222 expectedPath: "metadata.annotations",
223 expectedSubscript: "key",
224 expectedOK: true,
225 },
226 {
227 fieldPath: "metadata.annotations['a[b']c']",
228 expectedPath: "metadata.annotations",
229 expectedSubscript: "a[b']c",
230 expectedOK: true,
231 },
232 {
233 fieldPath: "metadata.labels['['key']",
234 expectedPath: "metadata.labels",
235 expectedSubscript: "['key",
236 expectedOK: true,
237 },
238 {
239 fieldPath: "metadata.labels['key']']",
240 expectedPath: "metadata.labels",
241 expectedSubscript: "key']",
242 expectedOK: true,
243 },
244 {
245 fieldPath: "metadata.labels['']",
246 expectedPath: "metadata.labels",
247 expectedSubscript: "",
248 expectedOK: true,
249 },
250 {
251 fieldPath: "metadata.labels[' ']",
252 expectedPath: "metadata.labels",
253 expectedSubscript: " ",
254 expectedOK: true,
255 },
256 {
257 fieldPath: "metadata.labels[ 'key' ]",
258 expectedOK: false,
259 },
260 {
261 fieldPath: "metadata.labels[]",
262 expectedOK: false,
263 },
264 {
265 fieldPath: "metadata.labels[']",
266 expectedOK: false,
267 },
268 {
269 fieldPath: "metadata.labels['key']foo",
270 expectedOK: false,
271 },
272 {
273 fieldPath: "['key']",
274 expectedOK: false,
275 },
276 {
277 fieldPath: "metadata.labels",
278 expectedOK: false,
279 },
280 }
281 for _, tc := range cases {
282 path, subscript, ok := SplitMaybeSubscriptedPath(tc.fieldPath)
283 if !ok {
284 if tc.expectedOK {
285 t.Errorf("SplitMaybeSubscriptedPath(%q) expected to return (_, _, true)", tc.fieldPath)
286 }
287 continue
288 }
289 if path != tc.expectedPath || subscript != tc.expectedSubscript {
290 t.Errorf("SplitMaybeSubscriptedPath(%q) = (%q, %q, true), expect (%q, %q, true)",
291 tc.fieldPath, path, subscript, tc.expectedPath, tc.expectedSubscript)
292 }
293 }
294 }
295
296
297 func TestFormatMap(t *testing.T) {
298 type args struct {
299 m map[string]string
300 }
301 tests := []struct {
302 name string
303 args args
304 wantFmtStr string
305 }{
306 {
307 name: "nil",
308 args: args{
309 m: nil,
310 },
311 wantFmtStr: "",
312 },
313 {
314 name: "label",
315 args: args{
316 m: map[string]string{
317 "beta.kubernetes.io/os": "linux",
318 "kubernetes.io/arch": "amd64",
319 "kubernetes.io/hostname": "master01",
320 "kubernetes.io/os": "linux",
321 "node-role.kubernetes.io/control-plane": "true",
322 "node-role.kubernetes.io/master": "true",
323 },
324 },
325 wantFmtStr: `beta.kubernetes.io/os="linux"
326 kubernetes.io/arch="amd64"
327 kubernetes.io/hostname="master01"
328 kubernetes.io/os="linux"
329 node-role.kubernetes.io/control-plane="true"
330 node-role.kubernetes.io/master="true"`,
331 },
332 {
333 name: "annotation",
334 args: args{
335 m: map[string]string{
336 "flannel.alpha.coreos.com/backend-data": `{"VNI":1,"VtepMAC":"ce:f9:c7:a4:de:64"}`,
337 "flannel.alpha.coreos.com/backend-type": "vxlan",
338 "flannel.alpha.coreos.com/kube-subnet-manager": "true",
339 "flannel.alpha.coreos.com/public-ip": "192.168.19.160",
340 "management.cattle.io/pod-limits": `{"cpu":"11400m","memory":"7965Mi"}`,
341 "management.cattle.io/pod-requests": `{"cpu":"2482m","memory":"7984Mi","pods":"26"}`,
342 "node.alpha.kubernetes.io/ttl": "0",
343 "volumes.kubernetes.io/controller-managed-attach-detach": "true",
344 },
345 },
346 wantFmtStr: `flannel.alpha.coreos.com/backend-data="{\"VNI\":1,\"VtepMAC\":\"ce:f9:c7:a4:de:64\"}"
347 flannel.alpha.coreos.com/backend-type="vxlan"
348 flannel.alpha.coreos.com/kube-subnet-manager="true"
349 flannel.alpha.coreos.com/public-ip="192.168.19.160"
350 management.cattle.io/pod-limits="{\"cpu\":\"11400m\",\"memory\":\"7965Mi\"}"
351 management.cattle.io/pod-requests="{\"cpu\":\"2482m\",\"memory\":\"7984Mi\",\"pods\":\"26\"}"
352 node.alpha.kubernetes.io/ttl="0"
353 volumes.kubernetes.io/controller-managed-attach-detach="true"`,
354 },
355 }
356 for _, tt := range tests {
357 t.Run(tt.name, func(t *testing.T) {
358 if gotFmtStr := FormatMap(tt.args.m); gotFmtStr != tt.wantFmtStr {
359 t.Errorf("FormatMap() = %v, want %v", gotFmtStr, tt.wantFmtStr)
360 }
361 })
362 }
363 }
364
View as plain text