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 }