github.com/cloudwego/kitex@v0.9.0/pkg/generic/reflect_test/reflect_test.go (about) 1 /* 2 * Copyright 2023 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package test 18 19 import ( 20 "context" 21 "errors" 22 "math/rand" 23 "net" 24 "os" 25 "runtime" 26 "strconv" 27 "sync" 28 "testing" 29 "time" 30 31 "github.com/cloudwego/kitex/client" 32 "github.com/cloudwego/kitex/client/callopt" 33 "github.com/cloudwego/kitex/client/genericclient" 34 "github.com/cloudwego/kitex/internal/test" 35 "github.com/cloudwego/kitex/pkg/generic" 36 "github.com/cloudwego/kitex/pkg/klog" 37 "github.com/cloudwego/kitex/server" 38 "github.com/cloudwego/kitex/server/genericserver" 39 40 "github.com/apache/thrift/lib/go/thrift" 41 dt "github.com/cloudwego/dynamicgo/thrift" 42 dg "github.com/cloudwego/dynamicgo/thrift/generic" 43 ) 44 45 func TestMain(m *testing.M) { 46 if runtime.GOOS == "windows" { 47 klog.Infof("skip generic reflect_test on windows") 48 return 49 } 50 initExampleDescriptor() 51 addr := test.GetLocalAddress() 52 svr := initServer(addr) 53 cli = initClient(addr) 54 55 mAddr := test.GetLocalAddress() 56 msvr := initThriftMapServer(mAddr, "./idl/example.thrift", new(exampeServerImpl)) 57 mcli = initThriftMapClient(mAddr, "./idl/example.thrift") 58 59 ret := m.Run() 60 61 cli.Close() 62 mcli.Close() 63 svr.Stop() 64 msvr.Stop() 65 66 os.Exit(ret) 67 } 68 69 var ( 70 SampleListSize = 100 71 SampleMapSize = 100 72 ) 73 74 func TestThriftReflectExample(t *testing.T) { 75 testThriftReflectExample_Node(t) 76 testThriftReflectExample_DOM(t) 77 } 78 79 func BenchmarkThriftReflectExample_Node(b *testing.B) { 80 for i := 0; i < b.N; i++ { 81 testThriftReflectExample_Node(b) 82 } 83 } 84 85 func BenchmarkThriftReflectExample_DOM(b *testing.B) { 86 for i := 0; i < b.N; i++ { 87 testThriftReflectExample_DOM(b) 88 } 89 } 90 91 func testThriftReflectExample_Node(t testing.TB) { 92 log_id := strconv.Itoa(rand.Int()) 93 94 // make a request body 95 req, err := makeExampleReqBinary(true, reqMsg, log_id) 96 if err != nil { 97 t.Fatal(err) 98 } 99 100 // wrap request as thrift CALL message 101 buf, err := dt.WrapBinaryBody(req, method, dt.CALL, 1, 0) 102 if err != nil { 103 t.Fatal(err) 104 } 105 106 // generic call 107 out, err := cli.GenericCall(context.Background(), method, buf, callopt.WithRPCTimeout(1*time.Second)) 108 if err != nil { 109 t.Fatal(err) 110 } 111 112 // println("data size:", len(out.([]byte)) + len(req), "B") 113 114 // unwrap REPLY message and get resp body 115 _, _, _, _, body, err := dt.UnwrapBinaryMessage(out.([]byte)) 116 if err != nil { 117 t.Fatal(err) 118 } 119 120 // biz logic... 121 err = exampleClientHandler_Node(body, log_id) 122 if err != nil { 123 t.Fatal(err) 124 } 125 } 126 127 func testThriftReflectExample_DOM(t testing.TB) { 128 log_id := strconv.Itoa(rand.Int()) 129 130 // make a request body 131 req, err := makeExampleReqBinary(true, reqMsg, log_id) 132 if err != nil { 133 t.Fatal(err) 134 } 135 136 // wrap request as thrift CALL message 137 buf, err := dt.WrapBinaryBody(req, method, dt.CALL, 1, 0) 138 if err != nil { 139 t.Fatal(err) 140 } 141 142 // generic call 143 out, err := cli.GenericCall(context.Background(), method, buf, callopt.WithRPCTimeout(1*time.Second)) 144 if err != nil { 145 t.Fatal(err) 146 } 147 148 // unwrap REPLY message and get resp body 149 _, _, _, _, body, err := dt.UnwrapBinaryMessage(out.([]byte)) 150 if err != nil { 151 t.Fatal(err) 152 } 153 154 // biz logic... 155 err = exampleClientHandler_DOM(body, log_id) 156 if err != nil { 157 t.Fatal(err) 158 } 159 } 160 161 var ( 162 exampleReqDesc *dt.TypeDescriptor 163 exampleRespDesc *dt.TypeDescriptor 164 strDesc *dt.TypeDescriptor 165 baseLogidPath []dg.Path 166 dynamicgoOptions = &dg.Options{} 167 ) 168 169 func initExampleDescriptor() { 170 sdesc, err := dt.NewDescritorFromPath(context.Background(), "idl/example.thrift") 171 if err != nil { 172 panic(err) 173 } 174 exampleReqDesc = sdesc.Functions()["ExampleMethod"].Request().Struct().FieldById(1).Type() 175 exampleRespDesc = sdesc.Functions()["ExampleMethod"].Response().Struct().FieldById(0).Type() 176 strDesc = exampleReqDesc.Struct().FieldById(1).Type() 177 baseLogidPath = []dg.Path{dg.NewPathFieldName("Base"), dg.NewPathFieldName("LogID")} 178 } 179 180 func initServer(addr string) server.Server { 181 // init special server 182 ip, _ := net.ResolveTCPAddr("tcp", addr) 183 g := generic.BinaryThriftGeneric() 184 svr := genericserver.NewServer(new(ExampleValueServiceImpl), g, 185 server.WithServiceAddr(ip), server.WithExitWaitTime(time.Millisecond*10)) 186 go func() { 187 err := svr.Run() 188 if err != nil { 189 panic(err) 190 } 191 }() 192 test.WaitServerStart(addr) 193 return svr 194 } 195 196 var cli genericclient.Client 197 198 func initClient(addr string) genericclient.Client { 199 g := generic.BinaryThriftGeneric() 200 genericCli, _ := genericclient.NewClient("destServiceName", g, client.WithHostPorts(addr)) 201 return genericCli 202 } 203 204 // makeExampleRespBinary make a Thrift-Binary-Encoding response using ExampleResp DOM 205 // Except msg, require_field and logid, which are reset everytime 206 func makeExampleRespBinary(msg, require_field, logid string) ([]byte, error) { 207 dom := &dg.PathNode{ 208 Node: dg.NewTypedNode(thrift.STRUCT, 0, 0), 209 Next: []dg.PathNode{ 210 { 211 Path: dg.NewPathFieldId(1), 212 Node: dg.NewNodeString(msg), 213 }, 214 { 215 Path: dg.NewPathFieldId(2), 216 Node: dg.NewNodeString(require_field), 217 }, 218 { 219 Path: dg.NewPathFieldId(255), 220 Node: dg.NewTypedNode(thrift.STRUCT, 0, 0), 221 Next: []dg.PathNode{ 222 { 223 Path: dg.NewPathFieldId(1), 224 Node: dg.NewNodeString(logid), 225 }, 226 }, 227 }, 228 }, 229 } 230 return dom.Marshal(dynamicgoOptions) 231 } 232 233 // makeExampleReqBinary make a Thrift-Binary-Encoding request using ExampleReq DOM 234 // Except B, A and logid, which are reset everytime 235 func makeExampleReqBinary(B bool, A, logid string) ([]byte, error) { 236 list := make([]dg.PathNode, SampleListSize+1) 237 list[0] = dg.PathNode{ 238 Path: dg.NewPathIndex(0), 239 Node: dg.NewTypedNode(thrift.STRUCT, 0, 0), 240 Next: []dg.PathNode{ 241 { 242 Path: dg.NewPathFieldId(1), 243 Node: dg.NewNodeString(A), 244 }, 245 }, 246 } 247 for i := 1; i < len(list); i++ { 248 list[i] = dg.PathNode{ 249 Path: dg.NewPathIndex(i), 250 Node: dg.NewTypedNode(thrift.STRUCT, 0, 0), 251 Next: []dg.PathNode{ 252 { 253 Path: dg.NewPathFieldId(1), 254 Node: dg.NewNodeString(A), 255 }, 256 }, 257 } 258 } 259 m := make([]dg.PathNode, SampleListSize+1) 260 m[0] = dg.PathNode{ 261 Path: dg.NewPathStrKey("a"), 262 Node: dg.NewTypedNode(thrift.STRUCT, 0, 0), 263 Next: []dg.PathNode{ 264 { 265 Path: dg.NewPathFieldId(1), 266 Node: dg.NewNodeString(A), 267 }, 268 }, 269 } 270 for i := 1; i < len(list); i++ { 271 list[i] = dg.PathNode{ 272 Path: dg.NewPathStrKey(strconv.Itoa(i)), 273 Node: dg.NewTypedNode(thrift.STRUCT, 0, 0), 274 Next: []dg.PathNode{ 275 { 276 Path: dg.NewPathFieldId(1), 277 Node: dg.NewNodeString(A), 278 }, 279 }, 280 } 281 } 282 283 dom := dg.PathNode{ 284 Node: dg.NewTypedNode(thrift.STRUCT, 0, 0), 285 Next: []dg.PathNode{ 286 { 287 Path: dg.NewPathFieldId(1), 288 Node: dg.NewNodeString("Hello"), 289 }, 290 { 291 Path: dg.NewPathFieldId(2), 292 Node: dg.NewNodeInt32(1), 293 }, 294 { 295 Path: dg.NewPathFieldId(3), 296 Node: dg.NewTypedNode(thrift.LIST, thrift.STRUCT, 0), 297 Next: list, 298 }, 299 { 300 Path: dg.NewPathFieldId(4), 301 Node: dg.NewTypedNode(thrift.MAP, thrift.STRUCT, thrift.STRING), 302 Next: m, 303 }, 304 { 305 Path: dg.NewPathFieldId(6), 306 Node: dg.NewTypedNode(thrift.LIST, thrift.I64, 0), 307 Next: []dg.PathNode{ 308 { 309 Path: dg.NewPathIndex(0), 310 Node: dg.NewNodeInt64(1), 311 }, 312 { 313 Path: dg.NewPathIndex(1), 314 Node: dg.NewNodeInt64(2), 315 }, 316 { 317 Path: dg.NewPathIndex(2), 318 Node: dg.NewNodeInt64(3), 319 }, 320 }, 321 }, 322 { 323 Path: dg.NewPathFieldId(7), 324 Node: dg.NewNodeBool(B), 325 }, 326 { 327 Path: dg.NewPathFieldId(255), 328 Node: dg.NewTypedNode(thrift.STRUCT, 0, 0), 329 Next: []dg.PathNode{ 330 { 331 Path: dg.NewPathFieldId(1), 332 Node: dg.NewNodeString(logid), 333 }, 334 { 335 Path: dg.NewPathFieldId(2), 336 Node: dg.NewNodeString("a.b.c"), 337 }, 338 { 339 Path: dg.NewPathFieldId(3), 340 Node: dg.NewNodeString("127.0.0.1"), 341 }, 342 { 343 Path: dg.NewPathFieldId(4), 344 Node: dg.NewNodeString("dynamicgo"), 345 }, 346 }, 347 }, 348 }, 349 } 350 return dom.Marshal(dynamicgoOptions) 351 } 352 353 const ( 354 method = "ExampleMethod2" 355 reqMsg = "pending" 356 respMsg = "ok" 357 ) 358 359 // ExampleValueServiceImpl ... 360 type ExampleValueServiceImpl struct{} 361 362 // GenericCall ... 363 func (g *ExampleValueServiceImpl) GenericCall(ctx context.Context, method string, request interface{}) (interface{}, error) { 364 // get and unwrap body with message 365 in := request.([]byte) 366 367 // unwarp thrift message and get request body 368 methodName, _, seqID, _, body, err := dt.UnwrapBinaryMessage(in) 369 if err != nil { 370 return nil, err 371 } 372 373 // biz logic 374 resp, err := exampleServerHandler(body) 375 if err != nil { 376 return nil, err 377 } 378 379 // wrap response as thrift REPLY message 380 return dt.WrapBinaryBody(resp, methodName, dt.REPLY, 0, seqID) 381 } 382 383 // biz logic 384 func exampleServerHandler(request []byte) (resp []byte, err error) { 385 // wrap body as Value 386 req := dg.NewValue(exampleReqDesc, request) 387 if err != nil { 388 return nil, err 389 } 390 391 required_field := "" 392 logid := "" 393 // if B == true then get logid and required_field 394 if b, err := req.FieldByName("B").Bool(); err == nil && b { 395 if e := req.GetByPath(baseLogidPath...); e.Error() != "" { 396 return nil, e 397 } else { 398 logid, _ = e.String() 399 } 400 if a := req.FieldByName("TestMap").GetByStr("a"); a.Error() != "" { 401 return nil, a 402 } else { 403 required_field, _ = a.FieldByName("Bar").String() 404 } 405 } 406 407 // make response with checked values 408 return makeExampleRespBinary(respMsg, required_field, logid) 409 } 410 411 var clientRespPool = sync.Pool{ 412 New: func() interface{} { 413 return &dg.PathNode{} 414 }, 415 } 416 417 // biz logic... 418 func exampleClientHandler_Node(response []byte, log_id string) error { 419 // make dynamicgo/generic.Node with body 420 resp := dg.NewNode(dt.STRUCT, response) 421 422 // check node values by Node APIs 423 msg, err := resp.Field(1).String() 424 if err != nil { 425 return err 426 } 427 if msg != respMsg { 428 return errors.New("msg does not match") 429 } 430 require_field, err := resp.Field(2).String() 431 if err != nil { 432 return err 433 } 434 if require_field != reqMsg { 435 return errors.New("require_field does not match") 436 } 437 438 return nil 439 } 440 441 func exampleClientHandler_DOM(response []byte, log_id string) error { 442 // get dom from memory pool 443 root := clientRespPool.Get().(*dg.PathNode) 444 root.Node = dg.NewNode(dt.STRUCT, response) 445 446 // load **first layer** children 447 err := root.Load(false, dynamicgoOptions) 448 if err != nil { 449 return err 450 } 451 // spew.Dump(root) // -- only root.Next is set 452 // check node values by PathNode APIs 453 require_field2, err := root.Field(2, dynamicgoOptions).Node.String() 454 if err != nil { 455 return err 456 } 457 if require_field2 != reqMsg { 458 return errors.New("require_field2 does not match") 459 } 460 461 // load **all layers** children 462 err = root.Load(true, dynamicgoOptions) 463 if err != nil { 464 return err 465 } 466 467 // spew.Dump(root) // -- every PathNode.Next will be set if it is a nesting-typed (LIST/SET/MAP/STRUCT) 468 // check node values by PathNode APIs 469 logid, err := root.Field(255, dynamicgoOptions).Field(1, dynamicgoOptions).Node.String() 470 if err != nil { 471 return err 472 } 473 if logid != log_id { 474 return errors.New("logid not match") 475 } 476 477 // recycle DOM 478 root.ResetValue() 479 clientRespPool.Put(root) 480 return nil 481 }