• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package proptools
16
17import (
18	"reflect"
19	"strings"
20	"testing"
21)
22
23type Named struct {
24	A *string `keep:"true"`
25	B *string
26}
27
28type NamedAllFiltered struct {
29	A *string
30}
31
32type NamedNoneFiltered struct {
33	A *string `keep:"true"`
34}
35
36func TestFilterPropertyStruct(t *testing.T) {
37	tests := []struct {
38		name     string
39		in       interface{}
40		out      interface{}
41		filtered bool
42	}{
43		// Property tests
44		{
45			name: "basic",
46			in: &struct {
47				A *string `keep:"true"`
48				B *string
49			}{},
50			out: &struct {
51				A *string
52			}{},
53			filtered: true,
54		},
55		{
56			name: "all filtered",
57			in: &struct {
58				A *string
59			}{},
60			out:      nil,
61			filtered: true,
62		},
63		{
64			name: "none filtered",
65			in: &struct {
66				A *string `keep:"true"`
67			}{},
68			out: &struct {
69				A *string `keep:"true"`
70			}{},
71			filtered: false,
72		},
73
74		// Sub-struct tests
75		{
76			name: "substruct",
77			in: &struct {
78				A struct {
79					A *string `keep:"true"`
80					B *string
81				} `keep:"true"`
82			}{},
83			out: &struct {
84				A struct {
85					A *string
86				}
87			}{},
88			filtered: true,
89		},
90		{
91			name: "substruct all filtered",
92			in: &struct {
93				A struct {
94					A *string
95				} `keep:"true"`
96			}{},
97			out:      nil,
98			filtered: true,
99		},
100		{
101			name: "substruct none filtered",
102			in: &struct {
103				A struct {
104					A *string `keep:"true"`
105				} `keep:"true"`
106			}{},
107			out: &struct {
108				A struct {
109					A *string `keep:"true"`
110				} `keep:"true"`
111			}{},
112			filtered: false,
113		},
114
115		// Named sub-struct tests
116		{
117			name: "named substruct",
118			in: &struct {
119				A Named `keep:"true"`
120			}{},
121			out: &struct {
122				A struct {
123					A *string
124				}
125			}{},
126			filtered: true,
127		},
128		{
129			name: "substruct all filtered",
130			in: &struct {
131				A NamedAllFiltered `keep:"true"`
132			}{},
133			out:      nil,
134			filtered: true,
135		},
136		{
137			name: "substruct none filtered",
138			in: &struct {
139				A NamedNoneFiltered `keep:"true"`
140			}{},
141			out: &struct {
142				A NamedNoneFiltered `keep:"true"`
143			}{},
144			filtered: false,
145		},
146
147		// Pointer to sub-struct tests
148		{
149			name: "pointer substruct",
150			in: &struct {
151				A *struct {
152					A *string `keep:"true"`
153					B *string
154				} `keep:"true"`
155			}{},
156			out: &struct {
157				A *struct {
158					A *string
159				}
160			}{},
161			filtered: true,
162		},
163		{
164			name: "pointer substruct all filtered",
165			in: &struct {
166				A *struct {
167					A *string
168				} `keep:"true"`
169			}{},
170			out:      nil,
171			filtered: true,
172		},
173		{
174			name: "pointer substruct none filtered",
175			in: &struct {
176				A *struct {
177					A *string `keep:"true"`
178				} `keep:"true"`
179			}{},
180			out: &struct {
181				A *struct {
182					A *string `keep:"true"`
183				} `keep:"true"`
184			}{},
185			filtered: false,
186		},
187
188		// Pointer to named sub-struct tests
189		{
190			name: "pointer named substruct",
191			in: &struct {
192				A *Named `keep:"true"`
193			}{},
194			out: &struct {
195				A *struct {
196					A *string
197				}
198			}{},
199			filtered: true,
200		},
201		{
202			name: "pointer substruct all filtered",
203			in: &struct {
204				A *NamedAllFiltered `keep:"true"`
205			}{},
206			out:      nil,
207			filtered: true,
208		},
209		{
210			name: "pointer substruct none filtered",
211			in: &struct {
212				A *NamedNoneFiltered `keep:"true"`
213			}{},
214			out: &struct {
215				A *NamedNoneFiltered `keep:"true"`
216			}{},
217			filtered: false,
218		},
219	}
220
221	for _, test := range tests {
222		t.Run(test.name, func(t *testing.T) {
223			out, filtered := FilterPropertyStruct(reflect.TypeOf(test.in),
224				func(field reflect.StructField, prefix string) (bool, reflect.StructField) {
225					if HasTag(field, "keep", "true") {
226						field.Tag = ""
227						return true, field
228					}
229					return false, field
230				})
231			if filtered != test.filtered {
232				t.Errorf("expected filtered %v, got %v", test.filtered, filtered)
233			}
234			expected := reflect.TypeOf(test.out)
235			if out != expected {
236				t.Errorf("expected type %v, got %v", expected, out)
237			}
238		})
239	}
240}
241
242func TestFilterPropertyStructSharded(t *testing.T) {
243	tests := []struct {
244		name        string
245		maxNameSize int
246		in          interface{}
247		out         []interface{}
248		filtered    bool
249	}{
250		// Property tests
251		{
252			name:        "basic",
253			maxNameSize: 20,
254			in: &struct {
255				A *string `keep:"true"`
256				B *string `keep:"true"`
257				C *string
258			}{},
259			out: []interface{}{
260				&struct {
261					A *string
262				}{},
263				&struct {
264					B *string
265				}{},
266			},
267			filtered: true,
268		},
269	}
270
271	for _, test := range tests {
272		t.Run(test.name, func(t *testing.T) {
273			out, filtered := filterPropertyStruct(reflect.TypeOf(test.in), "", test.maxNameSize,
274				func(field reflect.StructField, prefix string) (bool, reflect.StructField) {
275					if HasTag(field, "keep", "true") {
276						field.Tag = ""
277						return true, field
278					}
279					return false, field
280				})
281			if filtered != test.filtered {
282				t.Errorf("expected filtered %v, got %v", test.filtered, filtered)
283			}
284			var expected []reflect.Type
285			for _, t := range test.out {
286				expected = append(expected, reflect.TypeOf(t))
287			}
288			if !reflect.DeepEqual(out, expected) {
289				t.Errorf("expected type %v, got %v", expected, out)
290			}
291		})
292	}
293}
294
295func Test_fieldToTypeNameSize(t *testing.T) {
296	tests := []struct {
297		name  string
298		field reflect.StructField
299	}{
300		{
301			name: "string",
302			field: reflect.StructField{
303				Name: "Foo",
304				Type: reflect.TypeOf(""),
305			},
306		},
307		{
308			name: "string pointer",
309			field: reflect.StructField{
310				Name: "Foo",
311				Type: reflect.TypeOf(StringPtr("")),
312			},
313		},
314		{
315			name: "anonymous struct",
316			field: reflect.StructField{
317				Name: "Foo",
318				Type: reflect.TypeOf(struct{ foo string }{}),
319			},
320		}, {
321			name: "anonymous struct pointer",
322			field: reflect.StructField{
323				Name: "Foo",
324				Type: reflect.TypeOf(&struct{ foo string }{}),
325			},
326		},
327	}
328	for _, test := range tests {
329		t.Run(test.name, func(t *testing.T) {
330			typeName := reflect.StructOf([]reflect.StructField{test.field}).String()
331			typeName = strings.TrimPrefix(typeName, "struct { ")
332			typeName = strings.TrimSuffix(typeName, " }")
333			if g, w := fieldToTypeNameSize(test.field, true), len(typeName); g != w {
334				t.Errorf("want fieldToTypeNameSize(..., true) = %v, got %v", w, g)
335			}
336			if g, w := fieldToTypeNameSize(test.field, false), len(typeName)-len(test.field.Type.String()); g != w {
337				t.Errorf("want fieldToTypeNameSize(..., false) = %v, got %v", w, g)
338			}
339		})
340	}
341}
342
343func Test_filterPropertyStructFields(t *testing.T) {
344	type args struct {
345	}
346	tests := []struct {
347		name            string
348		maxTypeNameSize int
349		in              interface{}
350		out             []interface{}
351	}{
352		{
353			name:            "empty",
354			maxTypeNameSize: -1,
355			in:              struct{}{},
356			out:             nil,
357		},
358		{
359			name:            "one",
360			maxTypeNameSize: -1,
361			in: struct {
362				A *string
363			}{},
364			out: []interface{}{
365				struct {
366					A *string
367				}{},
368			},
369		},
370		{
371			name:            "two",
372			maxTypeNameSize: 20,
373			in: struct {
374				A *string
375				B *string
376			}{},
377			out: []interface{}{
378				struct {
379					A *string
380				}{},
381				struct {
382					B *string
383				}{},
384			},
385		},
386		{
387			name:            "nested",
388			maxTypeNameSize: 36,
389			in: struct {
390				AAAAA struct {
391					A string
392				}
393				BBBBB struct {
394					B string
395				}
396			}{},
397			out: []interface{}{
398				struct {
399					AAAAA struct {
400						A string
401					}
402				}{},
403				struct {
404					BBBBB struct {
405						B string
406					}
407				}{},
408			},
409		},
410		{
411			name:            "nested pointer",
412			maxTypeNameSize: 37,
413			in: struct {
414				AAAAA *struct {
415					A string
416				}
417				BBBBB *struct {
418					B string
419				}
420			}{},
421			out: []interface{}{
422				struct {
423					AAAAA *struct {
424						A string
425					}
426				}{},
427				struct {
428					BBBBB *struct {
429						B string
430					}
431				}{},
432			},
433		},
434		{
435			name:            "doubly nested",
436			maxTypeNameSize: 49,
437			in: struct {
438				AAAAA struct {
439					A struct {
440						A string
441					}
442				}
443				BBBBB struct {
444					B struct {
445						B string
446					}
447				}
448			}{},
449			out: []interface{}{
450				struct {
451					AAAAA struct {
452						A struct {
453							A string
454						}
455					}
456				}{},
457				struct {
458					BBBBB struct {
459						B struct {
460							B string
461						}
462					}
463				}{},
464			},
465		},
466	}
467	for _, test := range tests {
468		t.Run(test.name, func(t *testing.T) {
469			inType := reflect.TypeOf(test.in)
470			var in []reflect.StructField
471			for i := 0; i < inType.NumField(); i++ {
472				in = append(in, inType.Field(i))
473			}
474
475			keep := func(field reflect.StructField, string string) (bool, reflect.StructField) {
476				return true, field
477			}
478
479			// Test that maxTypeNameSize is the
480			if test.maxTypeNameSize > 0 {
481				correctPanic := false
482				func() {
483					defer func() {
484						if r := recover(); r != nil {
485							if _, ok := r.(cantFitPanic); ok {
486								correctPanic = true
487							} else {
488								panic(r)
489							}
490						}
491					}()
492
493					_, _ = filterPropertyStructFields(in, "", test.maxTypeNameSize-1, keep)
494				}()
495
496				if !correctPanic {
497					t.Errorf("filterPropertyStructFields() with size-1 should produce cantFitPanic")
498				}
499			}
500
501			filteredFieldsShards, _ := filterPropertyStructFields(in, "", test.maxTypeNameSize, keep)
502
503			var out []interface{}
504			for _, filteredFields := range filteredFieldsShards {
505				typ := reflect.StructOf(filteredFields)
506				if test.maxTypeNameSize > 0 && len(typ.String()) > test.maxTypeNameSize {
507					t.Errorf("out %q expected size <= %d, got %d",
508						typ.String(), test.maxTypeNameSize, len(typ.String()))
509				}
510				out = append(out, reflect.Zero(typ).Interface())
511			}
512
513			if g, w := out, test.out; !reflect.DeepEqual(g, w) {
514				t.Errorf("filterPropertyStructFields() want %v, got %v", w, g)
515			}
516		})
517	}
518}
519