• 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	type KeepAllWithAReallyLongNameThatExceedsTheMaxNameSize struct {
244		A *string `keep:"true"`
245		B *string `keep:"true"`
246		C *string `keep:"true"`
247	}
248
249	tests := []struct {
250		name        string
251		maxNameSize int
252		in          interface{}
253		out         []interface{}
254		filtered    bool
255	}{
256		// Property tests
257		{
258			name:        "basic",
259			maxNameSize: 20,
260			in: &struct {
261				A *string `keep:"true"`
262				B *string `keep:"true"`
263				C *string
264			}{},
265			out: []interface{}{
266				&struct {
267					A *string
268				}{},
269				&struct {
270					B *string
271				}{},
272			},
273			filtered: true,
274		},
275		{
276			name:        "anonymous where all match but still needs sharding",
277			maxNameSize: 20,
278			in: &struct {
279				A *string `keep:"true"`
280				B *string `keep:"true"`
281				C *string `keep:"true"`
282			}{},
283			out: []interface{}{
284				&struct {
285					A *string
286				}{},
287				&struct {
288					B *string
289				}{},
290				&struct {
291					C *string
292				}{},
293			},
294			filtered: true,
295		},
296		{
297			name:        "named where all match but still needs sharding",
298			maxNameSize: 20,
299			in:          &KeepAllWithAReallyLongNameThatExceedsTheMaxNameSize{},
300			out: []interface{}{
301				&struct {
302					A *string
303				}{},
304				&struct {
305					B *string
306				}{},
307				&struct {
308					C *string
309				}{},
310			},
311			filtered: true,
312		},
313	}
314
315	for _, test := range tests {
316		t.Run(test.name, func(t *testing.T) {
317			out, filtered := filterPropertyStruct(reflect.TypeOf(test.in), "", test.maxNameSize,
318				func(field reflect.StructField, prefix string) (bool, reflect.StructField) {
319					if HasTag(field, "keep", "true") {
320						field.Tag = ""
321						return true, field
322					}
323					return false, field
324				})
325			if filtered != test.filtered {
326				t.Errorf("expected filtered %v, got %v", test.filtered, filtered)
327			}
328			var expected []reflect.Type
329			for _, t := range test.out {
330				expected = append(expected, reflect.TypeOf(t))
331			}
332			if !reflect.DeepEqual(out, expected) {
333				t.Errorf("expected type %v, got %v", expected, out)
334			}
335		})
336	}
337}
338
339func Test_fieldToTypeNameSize(t *testing.T) {
340	tests := []struct {
341		name  string
342		field reflect.StructField
343	}{
344		{
345			name: "string",
346			field: reflect.StructField{
347				Name: "Foo",
348				Type: reflect.TypeOf(""),
349			},
350		},
351		{
352			name: "string pointer",
353			field: reflect.StructField{
354				Name: "Foo",
355				Type: reflect.TypeOf(StringPtr("")),
356			},
357		},
358		{
359			name: "anonymous struct",
360			field: reflect.StructField{
361				Name: "Foo",
362				Type: reflect.TypeOf(struct{ foo string }{}),
363			},
364		}, {
365			name: "anonymous struct pointer",
366			field: reflect.StructField{
367				Name: "Foo",
368				Type: reflect.TypeOf(&struct{ foo string }{}),
369			},
370		},
371	}
372	for _, test := range tests {
373		t.Run(test.name, func(t *testing.T) {
374			typeName := reflect.StructOf([]reflect.StructField{test.field}).String()
375			typeName = strings.TrimPrefix(typeName, "struct { ")
376			typeName = strings.TrimSuffix(typeName, " }")
377			if g, w := fieldToTypeNameSize(test.field, true), len(typeName); g != w {
378				t.Errorf("want fieldToTypeNameSize(..., true) = %v, got %v", w, g)
379			}
380			if g, w := fieldToTypeNameSize(test.field, false), len(typeName)-len(test.field.Type.String()); g != w {
381				t.Errorf("want fieldToTypeNameSize(..., false) = %v, got %v", w, g)
382			}
383		})
384	}
385}
386
387func Test_filterPropertyStructFields(t *testing.T) {
388	type args struct {
389	}
390	tests := []struct {
391		name            string
392		maxTypeNameSize int
393		in              interface{}
394		out             []interface{}
395	}{
396		{
397			name:            "empty",
398			maxTypeNameSize: -1,
399			in:              struct{}{},
400			out:             nil,
401		},
402		{
403			name:            "one",
404			maxTypeNameSize: -1,
405			in: struct {
406				A *string
407			}{},
408			out: []interface{}{
409				struct {
410					A *string
411				}{},
412			},
413		},
414		{
415			name:            "two",
416			maxTypeNameSize: 20,
417			in: struct {
418				A *string
419				B *string
420			}{},
421			out: []interface{}{
422				struct {
423					A *string
424				}{},
425				struct {
426					B *string
427				}{},
428			},
429		},
430		{
431			name:            "nested",
432			maxTypeNameSize: 36,
433			in: struct {
434				AAAAA struct {
435					A string
436				}
437				BBBBB struct {
438					B string
439				}
440			}{},
441			out: []interface{}{
442				struct {
443					AAAAA struct {
444						A string
445					}
446				}{},
447				struct {
448					BBBBB struct {
449						B string
450					}
451				}{},
452			},
453		},
454		{
455			name:            "nested pointer",
456			maxTypeNameSize: 37,
457			in: struct {
458				AAAAA *struct {
459					A string
460				}
461				BBBBB *struct {
462					B string
463				}
464			}{},
465			out: []interface{}{
466				struct {
467					AAAAA *struct {
468						A string
469					}
470				}{},
471				struct {
472					BBBBB *struct {
473						B string
474					}
475				}{},
476			},
477		},
478		{
479			name:            "doubly nested",
480			maxTypeNameSize: 49,
481			in: struct {
482				AAAAA struct {
483					A struct {
484						A string
485					}
486				}
487				BBBBB struct {
488					B struct {
489						B string
490					}
491				}
492			}{},
493			out: []interface{}{
494				struct {
495					AAAAA struct {
496						A struct {
497							A string
498						}
499					}
500				}{},
501				struct {
502					BBBBB struct {
503						B struct {
504							B string
505						}
506					}
507				}{},
508			},
509		},
510	}
511	for _, test := range tests {
512		t.Run(test.name, func(t *testing.T) {
513			inType := reflect.TypeOf(test.in)
514			var in []reflect.StructField
515			for i := 0; i < inType.NumField(); i++ {
516				in = append(in, inType.Field(i))
517			}
518
519			keep := func(field reflect.StructField, string string) (bool, reflect.StructField) {
520				return true, field
521			}
522
523			// Test that maxTypeNameSize is the
524			if test.maxTypeNameSize > 0 {
525				correctPanic := false
526				func() {
527					defer func() {
528						if r := recover(); r != nil {
529							if _, ok := r.(cantFitPanic); ok {
530								correctPanic = true
531							} else {
532								panic(r)
533							}
534						}
535					}()
536
537					_, _ = filterPropertyStructFields(in, "", test.maxTypeNameSize-1, keep)
538				}()
539
540				if !correctPanic {
541					t.Errorf("filterPropertyStructFields() with size-1 should produce cantFitPanic")
542				}
543			}
544
545			filteredFieldsShards, _ := filterPropertyStructFields(in, "", test.maxTypeNameSize, keep)
546
547			var out []interface{}
548			for _, filteredFields := range filteredFieldsShards {
549				typ := reflect.StructOf(filteredFields)
550				if test.maxTypeNameSize > 0 && len(typ.String()) > test.maxTypeNameSize {
551					t.Errorf("out %q expected size <= %d, got %d",
552						typ.String(), test.maxTypeNameSize, len(typ.String()))
553				}
554				out = append(out, reflect.Zero(typ).Interface())
555			}
556
557			if g, w := out, test.out; !reflect.DeepEqual(g, w) {
558				t.Errorf("filterPropertyStructFields() want %v, got %v", w, g)
559			}
560		})
561	}
562}
563