github.com/mitranim/gg@v0.1.17/reflect_test.go (about) 1 package gg_test 2 3 import ( 4 "fmt" 5 r "reflect" 6 "testing" 7 u "unsafe" 8 9 "github.com/mitranim/gg" 10 "github.com/mitranim/gg/gtest" 11 ) 12 13 func TestType(t *testing.T) { 14 defer gtest.Catch(t) 15 16 testType[int]() 17 testType[*int]() 18 testType[**int]() 19 20 testType[string]() 21 testType[string]() 22 testType[*string]() 23 testType[**string]() 24 25 testType[SomeModel]() 26 testType[*SomeModel]() 27 testType[**SomeModel]() 28 29 testType[func()]() 30 31 testTypeIface[any](r.TypeOf((*any)(nil)).Elem()) 32 testTypeIface[fmt.Stringer](r.TypeOf((*fmt.Stringer)(nil)).Elem()) 33 } 34 35 func testType[A any]() { 36 gtest.AnyEq(gg.Type[A](), r.TypeOf(gg.Zero[A]())) 37 } 38 39 func testTypeIface[A any](exp r.Type) { 40 gtest.AnyEq(gg.Type[A](), exp) 41 } 42 43 func TestTypeOf(t *testing.T) { 44 defer gtest.Catch(t) 45 46 testTypeOf(int(0)) 47 testTypeOf(int(10)) 48 testTypeOf((*int)(nil)) 49 testTypeOf((**int)(nil)) 50 51 testTypeOf(string(``)) 52 testTypeOf(string(`str`)) 53 testTypeOf((*string)(nil)) 54 testTypeOf((**string)(nil)) 55 56 testTypeOf(SomeModel{}) 57 testTypeOf((*SomeModel)(nil)) 58 testTypeOf((**SomeModel)(nil)) 59 60 testTypeOf((func())(nil)) 61 62 testTypeOfIface(any(nil), r.TypeOf((*any)(nil)).Elem()) 63 testTypeOfIface(fmt.Stringer(nil), r.TypeOf((*fmt.Stringer)(nil)).Elem()) 64 } 65 66 func testTypeOf[A any](src A) { 67 gtest.Equal(gg.TypeOf(src), r.TypeOf(src)) 68 } 69 70 func testTypeOfIface[A any](src A, exp r.Type) { 71 gtest.Equal(gg.TypeOf(src), exp) 72 } 73 74 /* 75 This benchmark is defective. It fails to reproduce spurious escapes commonly 76 observed in code using this function. 77 */ 78 func Benchmark_reflect_TypeOf(b *testing.B) { 79 for ind := 0; ind < b.N; ind++ { 80 gg.Nop1(r.TypeOf(SomeModel{})) 81 } 82 } 83 84 func BenchmarkTypeOf(b *testing.B) { 85 for ind := 0; ind < b.N; ind++ { 86 gg.Nop1(gg.TypeOf(SomeModel{})) 87 } 88 } 89 90 func BenchmarkType(b *testing.B) { 91 for ind := 0; ind < b.N; ind++ { 92 gg.Nop1(gg.Type[SomeModel]()) 93 } 94 } 95 96 func TestKindOfAny(t *testing.T) { 97 defer gtest.Catch(t) 98 99 // Difference from `KindOf`. 100 testKindOfAny(any(nil), r.Invalid) 101 testKindOfAny(fmt.Stringer(nil), r.Invalid) 102 103 testKindOfAny(``, r.String) 104 testKindOfAny((*string)(nil), r.Pointer) 105 testKindOfAny(SomeModel{}, r.Struct) 106 testKindOfAny((*SomeModel)(nil), r.Pointer) 107 testKindOfAny([]string(nil), r.Slice) 108 testKindOfAny((*[]string)(nil), r.Pointer) 109 testKindOfAny((func())(nil), r.Func) 110 } 111 112 func testKindOfAny(src any, exp r.Kind) { 113 gtest.Eq(gg.KindOfAny(src), exp) 114 } 115 116 func TestKindOf(t *testing.T) { 117 defer gtest.Catch(t) 118 119 // Difference from `KindOfAny`. 120 testKindOf(any(nil), r.Interface) 121 testKindOf(fmt.Stringer(nil), r.Interface) 122 123 testKindOf(``, r.String) 124 testKindOf((*string)(nil), r.Pointer) 125 testKindOf(SomeModel{}, r.Struct) 126 testKindOf((*SomeModel)(nil), r.Pointer) 127 testKindOf([]string(nil), r.Slice) 128 testKindOf((*[]string)(nil), r.Pointer) 129 testKindOf((func())(nil), r.Func) 130 } 131 132 func testKindOf[A any](src A, exp r.Kind) { 133 gtest.Eq(gg.KindOf(src), exp) 134 } 135 136 func BenchmarkKindOf(b *testing.B) { 137 for ind := 0; ind < b.N; ind++ { 138 gg.Nop1(gg.KindOf(SomeModel{})) 139 } 140 } 141 142 func BenchmarkAnyToString_miss(b *testing.B) { 143 for ind := 0; ind < b.N; ind++ { 144 gg.Nop2(gg.AnyToString(SomeModel{})) 145 } 146 } 147 148 func BenchmarkAnyToString_hit(b *testing.B) { 149 for ind := 0; ind < b.N; ind++ { 150 gg.Nop2(gg.AnyToString([]byte(`hello world`))) 151 } 152 } 153 154 func BenchmarkStructFields_Init(b *testing.B) { 155 key := gg.Type[SomeModel]() 156 157 for ind := 0; ind < b.N; ind++ { 158 var tar gg.StructFields 159 tar.Init(key) 160 } 161 } 162 163 func TestStructFieldCache(t *testing.T) { 164 typ := gg.Type[SomeModel]() 165 166 gtest.NotEmpty(gg.StructFieldCache.Get(typ)) 167 168 gtest.Equal( 169 gg.StructFieldCache.Get(typ), 170 gg.Times(typ.NumField(), typ.Field), 171 ) 172 } 173 174 func BenchmarkStructFieldCache(b *testing.B) { 175 key := gg.Type[SomeModel]() 176 177 for ind := 0; ind < b.N; ind++ { 178 gg.Nop1(gg.StructFieldCache.Get(key)) 179 } 180 } 181 182 func TestIsIndirect(t *testing.T) { 183 defer gtest.Catch(t) 184 185 gtest.False(gg.IsIndirect(gg.Type[bool]())) 186 gtest.False(gg.IsIndirect(gg.Type[int]())) 187 gtest.False(gg.IsIndirect(gg.Type[string]())) 188 gtest.False(gg.IsIndirect(gg.Type[[0]bool]())) 189 gtest.False(gg.IsIndirect(gg.Type[[1]bool]())) 190 gtest.False(gg.IsIndirect(gg.Type[[0]*string]())) 191 gtest.False(gg.IsIndirect(gg.Type[StructDirect]())) 192 gtest.False(gg.IsIndirect(gg.Type[func()]())) 193 gtest.False(gg.IsIndirect(gg.Type[func() bool]())) 194 gtest.False(gg.IsIndirect(gg.Type[func() *string]())) 195 gtest.False(gg.IsIndirect(gg.Type[chan bool]())) 196 gtest.False(gg.IsIndirect(gg.Type[chan *string]())) 197 198 gtest.True(gg.IsIndirect(gg.Type[any]())) 199 gtest.True(gg.IsIndirect(gg.Type[fmt.Stringer]())) 200 gtest.True(gg.IsIndirect(gg.Type[[1]*string]())) 201 gtest.True(gg.IsIndirect(gg.Type[[]byte]())) 202 gtest.True(gg.IsIndirect(gg.Type[[]string]())) 203 gtest.True(gg.IsIndirect(gg.Type[[]*string]())) 204 gtest.True(gg.IsIndirect(gg.Type[*bool]())) 205 gtest.True(gg.IsIndirect(gg.Type[*StructDirect]())) 206 gtest.True(gg.IsIndirect(gg.Type[StructIndirect]())) 207 gtest.True(gg.IsIndirect(gg.Type[*StructIndirect]())) 208 gtest.True(gg.IsIndirect(gg.Type[map[bool]bool]())) 209 } 210 211 func TestCloneDeep(t *testing.T) { 212 defer gtest.Catch(t) 213 214 t.Run(`direct`, func(t *testing.T) { 215 defer gtest.Catch(t) 216 217 testCloneDeepSame(true) 218 testCloneDeepSame(10) 219 testCloneDeepSame(`str`) 220 testCloneDeepSame([0]string{}) 221 testCloneDeepSame([2]string{`one`, `two`}) 222 testCloneDeepSame([0]*string{}) 223 224 // Private fields are ignored. 225 testCloneDeepSame(StructDirect{ 226 Public0: 10, 227 Public1: `one`, 228 private: gg.Ptr(`two`), 229 }) 230 }) 231 232 t.Run(`pointer`, func(t *testing.T) { 233 defer gtest.Catch(t) 234 235 gtest.Eq(gg.CloneDeep((*string)(nil)), (*string)(nil)) 236 237 { 238 src := gg.Ptr(`one`) 239 out := gg.CloneDeep(src) 240 241 gtest.NotEq(out, src) 242 243 *src = `two` 244 gtest.Equal(src, gg.Ptr(`two`)) 245 gtest.Equal(out, gg.Ptr(`one`)) 246 } 247 248 { 249 src := gg.Ptr(gg.Ptr(`one`)) 250 out := gg.CloneDeep(src) 251 252 gtest.NotEq(out, src) 253 gtest.NotEq(*out, *src) 254 255 **src = `two` 256 gtest.Equal(src, gg.Ptr(gg.Ptr(`two`))) 257 gtest.Equal(out, gg.Ptr(gg.Ptr(`one`))) 258 } 259 }) 260 261 t.Run(`slice`, func(t *testing.T) { 262 defer gtest.Catch(t) 263 264 testCloneDeepSameSlice([]string(nil)) 265 testCloneDeepSameSlice([]string{}) 266 267 // Slices with zero length but non-zero capacity must still be cloned. 268 testCloneDeepDifferentSlice([]string{`one`, `two`}[:0]) 269 testCloneDeepDifferentSlice([]*string{gg.Ptr(`one`), gg.Ptr(`two`)}[:0]) 270 271 { 272 src := []string{`one`, `two`} 273 out := gg.CloneDeep(src) 274 275 testCloneDeepDifferentSlice(src) 276 277 src[0] = `three` 278 gtest.Equal(src, []string{`three`, `two`}) 279 gtest.Equal(out, []string{`one`, `two`}) 280 } 281 282 { 283 src := []*string{gg.Ptr(`one`), gg.Ptr(`two`)} 284 out := gg.CloneDeep(src) 285 286 testCloneDeepDifferentSlice(src) 287 288 *src[0] = `three` 289 gtest.Equal(src, []*string{gg.Ptr(`three`), gg.Ptr(`two`)}) 290 gtest.Equal(out, []*string{gg.Ptr(`one`), gg.Ptr(`two`)}) 291 } 292 }) 293 294 t.Run(`slice_of_struct_pointers`, func(t *testing.T) { 295 defer gtest.Catch(t) 296 297 one := SomeModel{Id: 10} 298 two := SomeModel{Id: 20} 299 src := []*SomeModel{&one, &two} 300 out := gg.CloneDeep(src) 301 302 gtest.Equal(out, src) 303 304 one.Id = 30 305 two.Id = 40 306 src = append(src, &SomeModel{Id: 50}) 307 308 gtest.Equal( 309 src, 310 []*SomeModel{ 311 &SomeModel{Id: 30}, 312 &SomeModel{Id: 40}, 313 &SomeModel{Id: 50}, 314 }, 315 ) 316 317 gtest.Equal( 318 out, 319 []*SomeModel{ 320 &SomeModel{Id: 10}, 321 &SomeModel{Id: 20}, 322 }, 323 ) 324 }) 325 326 t.Run(`outer_interface`, func(t *testing.T) { 327 defer gtest.Catch(t) 328 329 src := []string{`one`, `two`} 330 tar := gg.CloneDeep(any(src)).([]string) 331 332 testSliceEqualButDistinct(src, tar) 333 }) 334 335 t.Run(`inner_interface`, func(t *testing.T) { 336 defer gtest.Catch(t) 337 338 type Type struct{ Val fmt.Stringer } 339 340 srcInner := gg.ErrStr(`one`) 341 src := Type{&srcInner} 342 out := gg.CloneDeep(src) 343 344 gtest.Equal(src, Type{gg.Ptr(gg.ErrStr(`one`))}) 345 gtest.Equal(out, Type{gg.Ptr(gg.ErrStr(`one`))}) 346 347 srcInner = `two` 348 349 gtest.Equal(src, Type{gg.Ptr(gg.ErrStr(`two`))}) 350 gtest.Equal(out, Type{gg.Ptr(gg.ErrStr(`one`))}) 351 }) 352 } 353 354 func testCloneDeepSame[A comparable](src A) { 355 gtest.Eq(gg.CloneDeep(src), src) 356 } 357 358 func testCloneDeepSameSlice[A any](src []A) { 359 gtest.Equal(gg.CloneDeep(src), src) 360 361 gtest.Eq(u.SliceData(gg.Clone(src)), u.SliceData(src)) 362 gtest.Eq(u.SliceData(gg.CloneDeep(src)), u.SliceData(src)) 363 364 gtest.SliceIs(gg.Clone(src), src) 365 gtest.SliceIs(gg.CloneDeep(src), src) 366 } 367 368 /* 369 Note: this doesn't verify the deep cloning of slice elements, which must be 370 checked separately. 371 */ 372 func testCloneDeepDifferentSlice[A any](src []A) { 373 testSliceEqualButDistinct(src, gg.CloneDeep(src)) 374 } 375 376 func testSliceEqualButDistinct[A any](src, tar []A) { 377 shallow := gg.Clone(src) 378 379 gtest.Equal(tar, src) 380 gtest.Equal(tar, shallow) 381 382 gtest.NotEq(u.SliceData(shallow), u.SliceData(src)) 383 gtest.NotEq(u.SliceData(tar), u.SliceData(src)) 384 385 gtest.NotSliceIs(shallow, src) 386 gtest.NotSliceIs(tar, src) 387 } 388 389 func Benchmark_clone_direct_CloneDeep(b *testing.B) { 390 src := [8]SomeModel{{Id: 10}, {Id: 20}, {Id: 30}} 391 gtest.Equal(gg.CloneDeep(src), src) 392 b.ResetTimer() 393 394 for ind := 0; ind < b.N; ind++ { 395 gg.Nop1(gg.CloneDeep(src)) 396 } 397 } 398 399 func Benchmark_clone_direct_native(b *testing.B) { 400 src := [8]SomeModel{{Id: 10}, {Id: 20}, {Id: 30}} 401 402 for ind := 0; ind < b.N; ind++ { 403 gg.Nop1(esc(src)) 404 } 405 } 406 407 func Benchmark_clone_slice_CloneDeep(b *testing.B) { 408 src := []SomeModel{{Id: 10}, {Id: 20}, {Id: 30}} 409 gtest.Equal(gg.CloneDeep(src), src) 410 b.ResetTimer() 411 412 for ind := 0; ind < b.N; ind++ { 413 gg.Nop1(gg.CloneDeep(src)) 414 } 415 } 416 417 func Benchmark_clone_slice_Clone(b *testing.B) { 418 src := []SomeModel{{Id: 10}, {Id: 20}, {Id: 30}} 419 gtest.Equal(gg.Clone(src), src) 420 b.ResetTimer() 421 422 for ind := 0; ind < b.N; ind++ { 423 gg.Nop1(gg.Clone(src)) 424 } 425 } 426 427 func Benchmark_clone_map_CloneDeep(b *testing.B) { 428 src := gg.Index( 429 []SomeModel{{Id: 10}, {Id: 20}, {Id: 30}}, 430 gg.ValidPk[SomeKey, SomeModel], 431 ) 432 gtest.Equal(gg.CloneDeep(src), src) 433 b.ResetTimer() 434 435 for ind := 0; ind < b.N; ind++ { 436 gg.Nop1(gg.CloneDeep(src)) 437 } 438 } 439 440 func Benchmark_clone_map_MapClone(b *testing.B) { 441 src := gg.Index( 442 []SomeModel{{Id: 10}, {Id: 20}, {Id: 30}}, 443 gg.ValidPk[SomeKey, SomeModel], 444 ) 445 gtest.Equal(gg.MapClone(src), src) 446 b.ResetTimer() 447 448 for ind := 0; ind < b.N; ind++ { 449 gg.Nop1(gg.MapClone(src)) 450 } 451 } 452 453 func TestStructDeepPublicFieldCache(t *testing.T) { 454 defer gtest.Catch(t) 455 456 gtest.Zero(gg.StructDeepPublicFieldCache.Get(gg.Type[struct{}]())) 457 458 gtest.Equal( 459 gg.StructDeepPublicFieldCache.Get(gg.Type[StructDirect]()), 460 gg.StructDeepPublicFields{ 461 { 462 Name: `Public0`, 463 Type: gg.Type[int](), 464 Offset: 0, 465 Index: []int{0}, 466 }, 467 { 468 Name: `Public1`, 469 Type: gg.Type[string](), 470 Offset: u.Offsetof(StructDirect{}.Public1), 471 Index: []int{1}, 472 }, 473 }, 474 ) 475 476 gtest.Equal( 477 gg.StructDeepPublicFieldCache.Get(gg.Type[Outer]()), 478 gg.StructDeepPublicFields{ 479 { 480 Name: `OuterId`, 481 Type: gg.Type[int](), 482 Index: []int{0}}, 483 { 484 Name: `OuterName`, 485 Type: gg.Type[string](), 486 Offset: u.Offsetof(Outer{}.OuterName), 487 Index: []int{1}}, 488 { 489 Name: `EmbedId`, 490 Type: gg.Type[int](), 491 Offset: u.Offsetof(Outer{}.Embed) + u.Offsetof(Embed{}.EmbedId), 492 Index: []int{2, 0}}, 493 { 494 Name: `EmbedName`, 495 Type: gg.Type[string](), 496 Offset: u.Offsetof(Outer{}.Embed) + u.Offsetof(Embed{}.EmbedName), 497 Index: []int{2, 1}}, 498 { 499 Name: `Inner`, 500 Type: gg.Type[*Inner](), 501 Offset: u.Offsetof(Outer{}.Inner), 502 Index: []int{3}}, 503 }, 504 ) 505 } 506 507 func TestJsonNameToDbNameCache(t *testing.T) { 508 defer gtest.Catch(t) 509 510 gtest.Zero(gg.JsonNameToDbNameCache.Get(gg.Type[struct{}]())) 511 512 gtest.Equal( 513 gg.JsonNameToDbNameCache.Get(gg.Type[SomeJsonDbMapper]()), 514 gg.JsonNameToDbName{ 515 `someName`: `some_name`, 516 `someValue`: `some_value`, 517 }, 518 ) 519 } 520 521 func TestDbNameToJsonNameCache(t *testing.T) { 522 defer gtest.Catch(t) 523 524 gtest.Zero(gg.DbNameToJsonNameCache.Get(gg.Type[struct{}]())) 525 526 gtest.Equal( 527 gg.DbNameToJsonNameCache.Get(gg.Type[SomeJsonDbMapper]()), 528 gg.DbNameToJsonName{ 529 `some_name`: `someName`, 530 `some_value`: `someValue`, 531 }, 532 ) 533 } 534 535 func TestValueDeref(t *testing.T) { 536 defer gtest.Catch(t) 537 538 testZero := func(src any) { 539 gtest.Eq(gg.ValueDeref(r.ValueOf(src)), r.Value{}) 540 } 541 542 testEq := func(src, exp any) { 543 gtest.Equal( 544 gg.ValueDeref(r.ValueOf(src)).Interface(), 545 exp, 546 ) 547 } 548 549 testZero(nil) 550 testZero((*string)(nil)) 551 testZero((**string)(nil)) 552 testZero(gg.Ptr((*string)(nil))) 553 testZero(gg.Ptr(gg.Ptr((*string)(nil)))) 554 555 testEq(`str`, `str`) 556 testEq(gg.Ptr(`str`), `str`) 557 testEq(gg.Ptr(gg.Ptr(`str`)), `str`) 558 testEq(gg.Ptr(gg.Ptr(`str`)), `str`) 559 testEq(gg.Ptr(gg.Ptr(gg.Ptr(`str`))), `str`) 560 } 561 562 func TestValueDerefAlloc(t *testing.T) { 563 defer gtest.Catch(t) 564 565 deref := func(src any) r.Value { 566 return gg.ValueDerefAlloc(r.ValueOf(src)) 567 } 568 569 testZero := func(src any) { gtest.Eq(deref(src), r.Value{}) } 570 571 testZero(nil) 572 testZero((*string)(nil)) 573 testZero((**string)(nil)) 574 575 { 576 var tar string 577 gtest.Equal(deref(&tar).Interface(), any(``)) 578 } 579 580 { 581 var tar *string 582 gtest.Equal(deref(&tar).Interface(), any(``)) 583 gtest.Equal(tar, gg.Ptr(``)) 584 585 deref(&tar).SetString(`str`) 586 gtest.Equal(tar, gg.Ptr(`str`)) 587 } 588 589 { 590 var tar **string 591 gtest.Equal(deref(&tar).Interface(), any(``)) 592 gtest.Equal(tar, gg.Ptr(gg.Ptr(``))) 593 594 deref(&tar).SetString(`str`) 595 gtest.Equal(tar, gg.Ptr(gg.Ptr(`str`))) 596 } 597 } 598 599 // Compare `BenchmarkSize`. 600 func Benchmark_size_reflect(b *testing.B) { 601 defer gtest.Catch(b) 602 603 for ind := 0; ind < b.N; ind++ { 604 gg.Nop1(Size[string]()) 605 } 606 } 607 608 // Runs slower than an equivalent version using `unsafe`. 609 func Size[A any]() uintptr { return gg.Type[A]().Size() }