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