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() }