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