github.com/podhmo/reflect-shape@v0.4.3/api_test.go (about)

     1  package reflectshape_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"testing"
     8  
     9  	"github.com/google/go-cmp/cmp"
    10  	reflectshape "github.com/podhmo/reflect-shape"
    11  )
    12  
    13  type S0 struct{}
    14  type S1 struct{}
    15  
    16  func F0()         {}
    17  func F1()         {}
    18  func (s0 S0) M()  {}
    19  func (s1 *S1) M() {}
    20  
    21  var cfg = &reflectshape.Config{IncludeGoTestFiles: true}
    22  
    23  func TestIdentity(t *testing.T) {
    24  	type testcase struct {
    25  		msg string
    26  		x   any
    27  		y   any
    28  	}
    29  
    30  	t.Run("equal", func(t *testing.T) {
    31  		cases := []testcase{
    32  			{msg: "same-struct", x: S0{}, y: S0{}},
    33  			{msg: "same-struct-pointer", x: S0{}, y: &S0{}},
    34  			{msg: "same-function", x: F0, y: F0},
    35  			{msg: "same-method", x: new(S0).M, y: new(S0).M},
    36  			{msg: "same-method-pointer", x: new(S0).M, y: (S0{}).M},
    37  		}
    38  
    39  		cfg := &reflectshape.Config{}
    40  		for _, c := range cases {
    41  			t.Run(c.msg, func(t *testing.T) {
    42  				x := cfg.Extract(c.x)
    43  				y := cfg.Extract(c.y)
    44  				if !x.Equal(y) {
    45  					t.Errorf("Shape.ID, must be %v == %v", c.x, c.y)
    46  				}
    47  			})
    48  		}
    49  	})
    50  
    51  	t.Run("not-equal", func(t *testing.T) {
    52  		cases := []testcase{
    53  			{msg: "another-struct", x: S0{}, y: S1{}},
    54  			{msg: "another-function", x: F0, y: F1},
    55  			{msg: "another-method", x: new(S1).M, y: new(S0).M},
    56  			{msg: "function-and-method", x: F0, y: new(S0).M},
    57  		}
    58  
    59  		for _, c := range cases {
    60  			t.Run(c.msg, func(t *testing.T) {
    61  				x := cfg.Extract(c.x)
    62  				y := cfg.Extract(c.y)
    63  				if x.Equal(y) {
    64  					t.Errorf("Shape.ID, must be %v != %v", c.x, c.y)
    65  				}
    66  			})
    67  		}
    68  	})
    69  }
    70  func TestPointerLevel(t *testing.T) {
    71  	type testcase struct {
    72  		msg   string
    73  		input any
    74  		lv    int
    75  	}
    76  
    77  	cases := []testcase{
    78  		{msg: "zero", input: S0{}, lv: 0},
    79  		{msg: "one", input: &S0{}, lv: 1},
    80  		{msg: "two", input: func() **S0 { s := new(S0); return &s }(), lv: 2},
    81  		{msg: "zero-int", input: 0, lv: 0},
    82  		{msg: "zero-slice", input: []S0{}, lv: 0},
    83  		{msg: "one-slice", input: &[]S0{}, lv: 1},
    84  	}
    85  
    86  	for _, c := range cases {
    87  		t.Run(c.msg, func(t *testing.T) {
    88  			s := cfg.Extract(c.input)
    89  			if want, got := c.lv, s.Lv; want != got {
    90  				t.Errorf("Shape.Lv, must be want:%v == got:%v", want, got)
    91  			}
    92  		})
    93  	}
    94  }
    95  
    96  func TestPackagePath(t *testing.T) {
    97  	cases := []struct {
    98  		msg     string
    99  		input   any
   100  		pkgpath string
   101  	}{
   102  		{msg: "struct", input: S0{}, pkgpath: "github.com/podhmo/reflect-shape_test"},
   103  		{msg: "struct-pointer", input: &S0{}, pkgpath: "github.com/podhmo/reflect-shape_test"},
   104  		{msg: "func", input: F0, pkgpath: "github.com/podhmo/reflect-shape_test"},
   105  		{msg: "slice", input: []S0{}, pkgpath: ""},
   106  		// stdlib
   107  		{msg: "int", input: int(0), pkgpath: ""},
   108  		{msg: "stdlib-func", input: t.Run, pkgpath: "testing"},
   109  	}
   110  	for _, c := range cases {
   111  		c := c
   112  		t.Run(c.msg, func(t *testing.T) {
   113  			shape := cfg.Extract(c.input)
   114  			if want, got := c.pkgpath, shape.Package.Path; want != got {
   115  				t.Errorf("Shape.Package.Path: %#+v != %#+v", want, got)
   116  			}
   117  		})
   118  	}
   119  }
   120  
   121  func TestPackageScopeNames(t *testing.T) {
   122  	t.Run("one", func(t *testing.T) {
   123  		want := []string{"F0"}
   124  
   125  		cfg := &reflectshape.Config{}
   126  		shape := cfg.Extract(F0)
   127  
   128  		if got := shape.Package.Scope().Names(); !reflect.DeepEqual(want, got) {
   129  			t.Errorf("Package.Names(): %#+v != %#+v", want, got)
   130  		}
   131  	})
   132  
   133  	t.Run("many", func(t *testing.T) {
   134  		want := []string{"F1", "S0", "S1"}
   135  
   136  		cfg := &reflectshape.Config{}
   137  
   138  		cfg.Extract(S0{})
   139  		cfg.Extract(&S0{})
   140  		cfg.Extract(&S1{})
   141  
   142  		cfg.Extract(new(S0).M) // ignored
   143  		cfg.Extract(new(S1).M) // ignored
   144  
   145  		// cfg.Extract(F0) // not seen
   146  		shape := cfg.Extract(F1)
   147  
   148  		if got := shape.Package.Scope().Names(); !reflect.DeepEqual(want, got) {
   149  			t.Errorf("Package.Names(): %#+v != %#+v", want, got)
   150  		}
   151  	})
   152  }
   153  
   154  // This is Foo.
   155  func Foo(ctx context.Context, name string, nickname *string) error {
   156  	return nil
   157  }
   158  
   159  // Foo's alternative that return variables are named.
   160  func FooWithRetNames(ctx context.Context, name string, nickname *string) (err error) {
   161  	return nil
   162  }
   163  
   164  // Foo's alternative that arguments are not named.
   165  func FooWithoutArgNames(context.Context, string, *string) error {
   166  	return nil
   167  }
   168  
   169  // Foo's alternative that variadic arguments.
   170  func FooWithVariadicArgs(ctx context.Context, name string, nickname *string, args ...any) error {
   171  	return nil
   172  }
   173  
   174  func TestFunc(t *testing.T) {
   175  	cases := []struct {
   176  		fn           any
   177  		args         []string
   178  		returns      []string
   179  		isMethod     bool
   180  		isVariadic   bool
   181  		fillArgNames bool
   182  	}{
   183  		{fn: Foo, args: []string{"ctx", "name", "nickname"}, returns: []string{""}},
   184  		{fn: FooWithRetNames, args: []string{"ctx", "name", "nickname"}, returns: []string{"err"}},
   185  		{fn: FooWithoutArgNames, args: []string{"", "", ""}, returns: []string{""}},
   186  		// isVariadic
   187  		{fn: FooWithVariadicArgs, args: []string{"ctx", "name", "nickname", "args"}, returns: []string{""}, isVariadic: true},
   188  		// isMethod
   189  		{fn: new(S0).M, args: nil, returns: nil, isMethod: true},
   190  		// fillArgNames
   191  		{fn: FooWithoutArgNames, args: []string{"ctx", "arg1", "arg2"}, returns: []string{"err"}, fillArgNames: true},
   192  	}
   193  
   194  	for i, c := range cases {
   195  		c := c
   196  		cfg := &reflectshape.Config{IncludeGoTestFiles: true, FillArgNames: c.fillArgNames, FillReturnNames: c.fillArgNames}
   197  		t.Run(fmt.Sprintf("case%d", i), func(t *testing.T) {
   198  			fn := cfg.Extract(c.fn).Func()
   199  			t.Logf("%s", fn)
   200  
   201  			{
   202  				var got []string
   203  				args := fn.Args()
   204  				for _, v := range args {
   205  					got = append(got, v.Name)
   206  				}
   207  
   208  				want := c.args
   209  				type ref struct{ XS []string }
   210  				if diff := cmp.Diff(ref{want}, ref{got}); diff != "" {
   211  					t.Errorf("Shape.Func().Args(): -want, +got: \n%v", diff)
   212  				}
   213  			}
   214  
   215  			{
   216  				var got []string
   217  				args := fn.Returns()
   218  				for _, v := range args {
   219  					got = append(got, v.Name)
   220  				}
   221  
   222  				want := c.returns
   223  				type ref struct{ XS []string }
   224  				if diff := cmp.Diff(ref{want}, ref{got}); diff != "" {
   225  					t.Errorf("Shape.Func().Returns(): -want, +got: \n%v", diff)
   226  				}
   227  			}
   228  
   229  			if want, got := c.isMethod, fn.IsMethod(); want != got {
   230  				t.Errorf("Shape.Func().IsMethod(): want:%v != got:%v", want, got)
   231  			}
   232  			if want, got := c.isVariadic, fn.IsVariadic(); want != got {
   233  				t.Errorf("Shape.Func().IsVariadic(): want:%v != got:%v", want, got)
   234  			}
   235  		})
   236  	}
   237  
   238  	t.Run("method-name", func(t *testing.T) {
   239  		want := "S0.M"
   240  		got := cfg.Extract(new(S0).M).Name
   241  		if diff := cmp.Diff(want, got); diff != "" {
   242  			t.Errorf("Shape.Func().Name: -want, +got: \n%v", diff)
   243  		}
   244  	})
   245  
   246  	t.Run("doc", func(t *testing.T) {
   247  		want := "This is Foo."
   248  		got := cfg.Extract(Foo).Func().Doc()
   249  		if diff := cmp.Diff(want, got); diff != "" {
   250  			t.Errorf("Shape.Func().Doc(): -want, +got: \n%v", diff)
   251  		}
   252  	})
   253  	// PANIC (not supported)
   254  	// fmt.Println(cfg.Extract(func(fmt string, args ...any) {}).MustFunc())
   255  }
   256  
   257  // Wrap type
   258  type Wrap[T any] struct {
   259  	Value T
   260  }
   261  
   262  // Person object
   263  type Person struct {
   264  	Name     string // name of person
   265  	Father   *Person
   266  	Children []*Person
   267  }
   268  
   269  func TestStruct(t *testing.T) {
   270  	cases := []struct {
   271  		ob     any
   272  		name   string
   273  		fields []string
   274  		docs   []string
   275  	}{
   276  		{name: "Person", ob: Person{}, fields: []string{"Name", "Father", "Children"}, docs: []string{"name of person", "", ""}},
   277  		{name: "Person", ob: &Person{}, fields: []string{"Name", "Father", "Children"}},
   278  	}
   279  
   280  	for i, c := range cases {
   281  		c := c
   282  		t.Run(fmt.Sprintf("case%d", i), func(t *testing.T) {
   283  			s := cfg.Extract(c.ob).Struct()
   284  			t.Logf("%s", s)
   285  
   286  			if want, got := c.name, s.Name(); want != got {
   287  				t.Errorf("Shape.Struct().Name():  want:%v != got:%v", want, got)
   288  			}
   289  
   290  			{
   291  				var got []string
   292  				fields := s.Fields()
   293  				for _, v := range fields {
   294  					got = append(got, v.Name)
   295  				}
   296  				if want := c.fields; !reflect.DeepEqual(want, got) {
   297  					t.Errorf("Shape.Struct().Fields(): names, want:%#+v != got:%#+v", want, got)
   298  				}
   299  			}
   300  
   301  			if c.docs != nil {
   302  				var got []string
   303  				fields := s.Fields()
   304  				for _, v := range fields {
   305  					got = append(got, v.Doc)
   306  				}
   307  				if want := c.docs; !reflect.DeepEqual(want, got) {
   308  					t.Errorf("Shape.Struct().Fields(): docs, want:%#+v != got:%#+v", want, got)
   309  				}
   310  			}
   311  		})
   312  	}
   313  
   314  	t.Run("doc-generics", func(t *testing.T) {
   315  		want := "Wrap type"
   316  		got := cfg.Extract(Wrap[int]{Value: 10}).Struct().Doc()
   317  		if diff := cmp.Diff(want, got); diff != "" {
   318  			t.Errorf("Shape.Struct().Doc(): -want, +got: \n%v", diff)
   319  		}
   320  	})
   321  }
   322  
   323  func UseContext(ctx context.Context) {}
   324  
   325  func TestInterface(t *testing.T) {
   326  	cases := []struct {
   327  		input   any
   328  		modify  func(*reflectshape.Shape) *reflectshape.Interface
   329  		name    string
   330  		methods []string
   331  	}{
   332  		{name: "Context", input: UseContext, methods: []string{"Deadline", "Done", "Err", "Value"},
   333  			modify: func(s *reflectshape.Shape) *reflectshape.Interface {
   334  				return s.Func().Args()[0].Shape.Interface()
   335  			}},
   336  	}
   337  
   338  	for i, c := range cases {
   339  		c := c
   340  		t.Run(fmt.Sprintf("case%d", i), func(t *testing.T) {
   341  			iface := c.modify(cfg.Extract(c.input))
   342  			t.Logf("%s", iface)
   343  
   344  			if want, got := c.name, iface.Name(); want != got {
   345  				t.Errorf("Shape.Interface().Name():  want:%v != got:%v", want, got)
   346  			}
   347  
   348  			{
   349  				var got []string
   350  				fields := iface.Methods()
   351  				for _, v := range fields {
   352  					got = append(got, v.Name)
   353  				}
   354  				if want := c.methods; !reflect.DeepEqual(want, got) {
   355  					t.Errorf("Shape.Interface().Methods(): names, want:%#+v != got:%#+v", want, got)
   356  				}
   357  			}
   358  		})
   359  	}
   360  }
   361  
   362  // Ordering is desc or asc
   363  type Ordering string
   364  
   365  func TestNamed(t *testing.T) {
   366  	cases := []struct {
   367  		input any
   368  		name  string
   369  		doc   string
   370  	}{
   371  		{input: Ordering("desc"), name: "Ordering", doc: "Ordering is desc or asc"},
   372  		{input: &Person{}, name: "Person", doc: "Person object"},
   373  	}
   374  
   375  	for i, c := range cases {
   376  		c := c
   377  		t.Run(fmt.Sprintf("case%d", i), func(t *testing.T) {
   378  			got := cfg.Extract(c.input).Named()
   379  			t.Logf("%s", got)
   380  
   381  			if want, got := c.name, got.Name(); want != got {
   382  				t.Errorf("Shape.Type().Name():  want:%v != got:%v", want, got)
   383  			}
   384  			if want, got := c.doc, got.Doc(); want != got {
   385  				t.Errorf("Shape.Type().Doc():  want:%v != got:%v", want, got)
   386  			}
   387  		})
   388  	}
   389  }