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  }