github.com/cloudwego/kitex@v0.9.0/pkg/generic/map_test/generic_test.go (about)

     1  /*
     2   * Copyright 2021 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  	"encoding/base64"
    22  	"net"
    23  	"reflect"
    24  	"runtime"
    25  	"runtime/debug"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/cloudwego/kitex/client/callopt"
    31  	"github.com/cloudwego/kitex/client/genericclient"
    32  	kt "github.com/cloudwego/kitex/internal/mocks/thrift"
    33  	"github.com/cloudwego/kitex/internal/test"
    34  	"github.com/cloudwego/kitex/pkg/generic"
    35  	"github.com/cloudwego/kitex/pkg/generic/descriptor"
    36  	"github.com/cloudwego/kitex/server"
    37  )
    38  
    39  func TestThrift(t *testing.T) {
    40  	addr := test.GetLocalAddress()
    41  	svr := initThriftServer(t, addr, new(GenericServiceImpl), false, false)
    42  	cli := initThriftClient(t, addr, false, false)
    43  
    44  	cases := []struct {
    45  		reqMsg   interface{}
    46  		wantResp interface{}
    47  	}{
    48  		{
    49  			reqMsg: map[string]interface{}{
    50  				"Msg": "hello",
    51  				"TestList": []interface{}{
    52  					map[string]interface{}{
    53  						"Bar": "foo",
    54  					},
    55  				},
    56  				"TestMap": map[interface{}]interface{}{
    57  					"l1": map[string]interface{}{
    58  						"Bar": "foo",
    59  					},
    60  				},
    61  				"StrList": []interface{}{
    62  					"123", "456",
    63  				},
    64  				"I64List": []interface{}{
    65  					int64(123), int64(456),
    66  				},
    67  				"B": true,
    68  			},
    69  			wantResp: map[string]interface{}{
    70  				"Msg": "hello",
    71  				"Foo": int32(0),
    72  				"TestList": []interface{}{
    73  					map[string]interface{}{
    74  						"Bar": "foo",
    75  					},
    76  				},
    77  				"TestMap": map[interface{}]interface{}{
    78  					"l1": map[string]interface{}{
    79  						"Bar": "foo",
    80  					},
    81  				},
    82  				"StrList": []interface{}{
    83  					"123", "456",
    84  				},
    85  				"I64List": []interface{}{
    86  					int64(123), int64(456),
    87  				},
    88  				"B": true,
    89  				"Base": map[string]interface{}{
    90  					"LogID":  "",
    91  					"Caller": "",
    92  					"Addr":   "",
    93  					"Client": "",
    94  				},
    95  			},
    96  		},
    97  		{
    98  			reqMsg: map[string]interface{}{
    99  				"Msg": "hello",
   100  				"TestList": []interface{}{
   101  					map[string]interface{}{
   102  						"Bar": "foo",
   103  					},
   104  					nil,
   105  				},
   106  				"TestMap": map[interface{}]interface{}{
   107  					"l1": map[string]interface{}{
   108  						"Bar": "foo",
   109  					},
   110  					"l2": nil,
   111  				},
   112  				"StrList": []interface{}{
   113  					"123", nil,
   114  				},
   115  				"I64List": []interface{}{
   116  					int64(123), nil,
   117  				},
   118  				"B": nil,
   119  			},
   120  			wantResp: map[string]interface{}{
   121  				"Msg": "hello",
   122  				"Foo": int32(0),
   123  				"TestList": []interface{}{
   124  					map[string]interface{}{
   125  						"Bar": "foo",
   126  					},
   127  					map[string]interface{}{
   128  						"Bar": "",
   129  					},
   130  				},
   131  				"TestMap": map[interface{}]interface{}{
   132  					"l1": map[string]interface{}{
   133  						"Bar": "foo",
   134  					},
   135  					"l2": map[string]interface{}{
   136  						"Bar": "",
   137  					},
   138  				},
   139  				"StrList": []interface{}{
   140  					"123", "",
   141  				},
   142  				"I64List": []interface{}{
   143  					int64(123), int64(0),
   144  				},
   145  				"B": false,
   146  				"Base": map[string]interface{}{
   147  					"LogID":  "",
   148  					"Caller": "",
   149  					"Addr":   "",
   150  					"Client": "",
   151  				},
   152  			},
   153  		},
   154  		{
   155  			reqMsg: map[string]interface{}{
   156  				"Msg": "hello",
   157  				"TestList": []interface{}{
   158  					nil,
   159  				},
   160  				"TestMap": map[interface{}]interface{}{
   161  					"l2": nil,
   162  				},
   163  				"StrList": []interface{}{
   164  					nil,
   165  				},
   166  				"I64List": []interface{}{
   167  					nil,
   168  				},
   169  				"B": nil,
   170  			},
   171  			wantResp: map[string]interface{}{
   172  				"Msg": "hello",
   173  				"Foo": int32(0),
   174  				"TestList": []interface{}{
   175  					map[string]interface{}{
   176  						"Bar": "",
   177  					},
   178  				},
   179  				"TestMap": map[interface{}]interface{}{
   180  					"l2": map[string]interface{}{
   181  						"Bar": "",
   182  					},
   183  				},
   184  				"StrList": []interface{}{
   185  					"",
   186  				},
   187  				"I64List": []interface{}{
   188  					int64(0),
   189  				},
   190  				"B": false,
   191  				"Base": map[string]interface{}{
   192  					"LogID":  "",
   193  					"Caller": "",
   194  					"Addr":   "",
   195  					"Client": "",
   196  				},
   197  			},
   198  		},
   199  		{
   200  			reqMsg: map[string]interface{}{
   201  				"Msg": "hello",
   202  				"TestList": []interface{}{
   203  					map[string]interface{}{
   204  						"Bar": "foo",
   205  					},
   206  					nil,
   207  				},
   208  				"TestMap": map[interface{}]interface{}{
   209  					"l1": map[string]interface{}{
   210  						"Bar": "foo",
   211  					},
   212  					"l2": nil,
   213  				},
   214  				"StrList": []interface{}{
   215  					"123", nil,
   216  				},
   217  				"I64List": []interface{}{
   218  					float64(123), nil, float64(456),
   219  				},
   220  				"B": nil,
   221  			},
   222  			wantResp: map[string]interface{}{
   223  				"Msg": "hello",
   224  				"Foo": int32(0),
   225  				"TestList": []interface{}{
   226  					map[string]interface{}{
   227  						"Bar": "foo",
   228  					},
   229  					map[string]interface{}{
   230  						"Bar": "",
   231  					},
   232  				},
   233  				"TestMap": map[interface{}]interface{}{
   234  					"l1": map[string]interface{}{
   235  						"Bar": "foo",
   236  					},
   237  					"l2": map[string]interface{}{
   238  						"Bar": "",
   239  					},
   240  				},
   241  				"StrList": []interface{}{
   242  					"123", "",
   243  				},
   244  				"I64List": []interface{}{
   245  					int64(123), int64(0), int64(456),
   246  				},
   247  				"B": false,
   248  				"Base": map[string]interface{}{
   249  					"LogID":  "",
   250  					"Caller": "",
   251  					"Addr":   "",
   252  					"Client": "",
   253  				},
   254  			},
   255  		},
   256  	}
   257  	for _, tcase := range cases {
   258  		resp, err := cli.GenericCall(context.Background(), "ExampleMethod", tcase.reqMsg, callopt.WithRPCTimeout(100*time.Second))
   259  		test.Assert(t, err == nil, err)
   260  		respMap, ok := resp.(map[string]interface{})
   261  		test.Assert(t, ok)
   262  		test.Assert(t, reflect.DeepEqual(respMap, tcase.wantResp), respMap)
   263  	}
   264  	svr.Stop()
   265  }
   266  
   267  func TestThriftPingMethod(t *testing.T) {
   268  	addr := test.GetLocalAddress()
   269  	svr := initThriftServer(t, addr, new(GenericServicePingImpl), false, false)
   270  	cli := initThriftClient(t, addr, false, false)
   271  
   272  	resp, err := cli.GenericCall(context.Background(), "Ping", "hello", callopt.WithRPCTimeout(100*time.Second))
   273  	test.Assert(t, err == nil, err)
   274  	respMap, ok := resp.(string)
   275  	test.Assert(t, ok)
   276  	test.Assert(t, reflect.DeepEqual(respMap, "hello"), respMap)
   277  	svr.Stop()
   278  }
   279  
   280  func TestThriftError(t *testing.T) {
   281  	addr := test.GetLocalAddress()
   282  	svr := initThriftServer(t, addr, new(GenericServiceErrorImpl), false, false)
   283  	cli := initThriftClient(t, addr, false, false)
   284  
   285  	_, err := cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
   286  	test.Assert(t, err != nil)
   287  	test.Assert(t, strings.Contains(err.Error(), errResp), err.Error())
   288  	svr.Stop()
   289  }
   290  
   291  func TestThriftOnewayMethod(t *testing.T) {
   292  	addr := test.GetLocalAddress()
   293  	svr := initThriftServer(t, addr, new(GenericServiceOnewayImpl), false, false)
   294  	cli := initThriftClient(t, addr, false, false)
   295  
   296  	resp, err := cli.GenericCall(context.Background(), "Oneway", "hello", callopt.WithRPCTimeout(100*time.Second))
   297  	test.Assert(t, err == nil, err)
   298  	test.Assert(t, resp == nil)
   299  	svr.Stop()
   300  }
   301  
   302  func TestThriftVoidMethod(t *testing.T) {
   303  	addr := test.GetLocalAddress()
   304  	svr := initThriftServer(t, addr, new(GenericServiceVoidImpl), false, false)
   305  	cli := initThriftClient(t, addr, false, false)
   306  
   307  	resp, err := cli.GenericCall(context.Background(), "Void", "hello", callopt.WithRPCTimeout(100*time.Second))
   308  	test.Assert(t, err == nil, err)
   309  	test.Assert(t, resp == descriptor.Void{})
   310  	svr.Stop()
   311  }
   312  
   313  func TestBase64Binary(t *testing.T) {
   314  	addr := test.GetLocalAddress()
   315  	svr := initThriftServer(t, addr, new(GenericServiceWithBase64Binary), true, false)
   316  	cli := initThriftClient(t, addr, true, false)
   317  
   318  	reqMsg = map[string]interface{}{"BinaryMsg": []byte("hello")}
   319  	resp, err := cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
   320  	test.Assert(t, err == nil, err)
   321  	gr, ok := resp.(map[string]interface{})
   322  	test.Assert(t, ok)
   323  	test.Assert(t, reflect.DeepEqual(gr["BinaryMsg"], base64.StdEncoding.EncodeToString([]byte("hello"))))
   324  
   325  	svr.Stop()
   326  }
   327  
   328  func TestBinaryWithByteSlice(t *testing.T) {
   329  	addr := test.GetLocalAddress()
   330  	svr := initThriftServer(t, addr, new(GenericServiceWithByteSliceImpl), false, true)
   331  	cli := initThriftClient(t, addr, false, false)
   332  
   333  	reqMsg = map[string]interface{}{"BinaryMsg": []byte("hello")}
   334  	resp, err := cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
   335  	test.Assert(t, err == nil, err)
   336  	gr, ok := resp.(map[string]interface{})
   337  	test.Assert(t, ok)
   338  	test.Assert(t, reflect.DeepEqual(gr["BinaryMsg"], "hello"))
   339  	svr.Stop()
   340  }
   341  
   342  func TestBinaryWithBase64AndByteSlice(t *testing.T) {
   343  	addr := test.GetLocalAddress()
   344  	svr := initThriftServer(t, addr, new(GenericServiceWithByteSliceImpl), true, true)
   345  	cli := initThriftClient(t, addr, true, true)
   346  
   347  	reqMsg = map[string]interface{}{"BinaryMsg": []byte("hello")}
   348  	resp, err := cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
   349  	test.Assert(t, err == nil, err)
   350  	gr, ok := resp.(map[string]interface{})
   351  	test.Assert(t, ok)
   352  	test.Assert(t, reflect.DeepEqual(gr["BinaryMsg"], []byte("hello")))
   353  	svr.Stop()
   354  }
   355  
   356  func TestThrift2NormalServer(t *testing.T) {
   357  	addr := test.GetLocalAddress()
   358  	svr := initMockServer(t, new(mockImpl), addr)
   359  	cli := initThriftMockClient(t, addr)
   360  
   361  	_, err := cli.GenericCall(context.Background(), "Test", mockReq, callopt.WithRPCTimeout(100*time.Second))
   362  	test.Assert(t, err == nil, err)
   363  	svr.Stop()
   364  }
   365  
   366  func initThriftMockClient(t *testing.T, address string) genericclient.Client {
   367  	p, err := generic.NewThriftFileProvider("./idl/mock.thrift")
   368  	test.Assert(t, err == nil)
   369  	g, err := generic.MapThriftGeneric(p)
   370  	test.Assert(t, err == nil)
   371  	cli := newGenericClient("destServiceName", g, address)
   372  	test.Assert(t, err == nil)
   373  	return cli
   374  }
   375  
   376  func initThriftClient(t *testing.T, addr string, base64Binary, byteSlice bool) genericclient.Client {
   377  	return initThriftClientByIDL(t, addr, "./idl/example.thrift", base64Binary, byteSlice)
   378  }
   379  
   380  func initThriftClientByIDL(t *testing.T, addr, idl string, base64Binary, byteSlice bool) genericclient.Client {
   381  	p, err := generic.NewThriftFileProvider(idl)
   382  	test.Assert(t, err == nil)
   383  	g, err := generic.MapThriftGeneric(p)
   384  	test.Assert(t, err == nil)
   385  	err = generic.SetBinaryWithBase64(g, base64Binary)
   386  	test.Assert(t, err == nil)
   387  	err = generic.SetBinaryWithByteSlice(g, byteSlice)
   388  	test.Assert(t, err == nil)
   389  	cli := newGenericClient("destServiceName", g, addr)
   390  	test.Assert(t, err == nil)
   391  	return cli
   392  }
   393  
   394  func initThriftServer(t *testing.T, address string, handler generic.Service, base64Binary, byteSlice bool) server.Server {
   395  	addr, _ := net.ResolveTCPAddr("tcp", address)
   396  	p, err := generic.NewThriftFileProvider("./idl/example.thrift")
   397  	test.Assert(t, err == nil)
   398  	g, err := generic.MapThriftGeneric(p)
   399  	test.Assert(t, err == nil)
   400  	err = generic.SetBinaryWithBase64(g, base64Binary)
   401  	test.Assert(t, err == nil)
   402  	err = generic.SetBinaryWithByteSlice(g, byteSlice)
   403  	test.Assert(t, err == nil)
   404  	svr := newGenericServer(g, addr, handler)
   405  	test.Assert(t, err == nil)
   406  	return svr
   407  }
   408  
   409  func initMockServer(t *testing.T, handler kt.Mock, address string) server.Server {
   410  	addr, _ := net.ResolveTCPAddr("tcp", address)
   411  	svr := newMockServer(handler, addr)
   412  	return svr
   413  }
   414  
   415  func TestMapThriftGenericClientClose(t *testing.T) {
   416  	debug.SetGCPercent(-1)
   417  	defer debug.SetGCPercent(100)
   418  
   419  	var ms runtime.MemStats
   420  	runtime.ReadMemStats(&ms)
   421  
   422  	t.Logf("Before new clients, allocation: %f Mb, Number of allocation: %d\n", mb(ms.HeapAlloc), ms.HeapObjects)
   423  
   424  	clientCnt := 1000
   425  	clis := make([]genericclient.Client, clientCnt)
   426  	for i := 0; i < clientCnt; i++ {
   427  		p, err := generic.NewThriftFileProvider("./idl/mock.thrift")
   428  		test.Assert(t, err == nil, "generic NewThriftFileProvider failed, err=%v", err)
   429  		g, err := generic.MapThriftGeneric(p)
   430  		test.Assert(t, err == nil, "generic MapThriftGeneric failed, err=%v", err)
   431  		clis[i] = newGenericClient("destServiceName", g, "127.0.0.1:9020")
   432  	}
   433  
   434  	runtime.ReadMemStats(&ms)
   435  	preHeapAlloc, preHeapObjects := mb(ms.HeapAlloc), ms.HeapObjects
   436  	t.Logf("After new clients, allocation: %f Mb, Number of allocation: %d\n", preHeapAlloc, preHeapObjects)
   437  
   438  	for _, cli := range clis {
   439  		_ = cli.Close()
   440  	}
   441  	runtime.GC()
   442  	runtime.ReadMemStats(&ms)
   443  	afterGCHeapAlloc, afterGCHeapObjects := mb(ms.HeapAlloc), ms.HeapObjects
   444  	t.Logf("After close clients and GC be executed, allocation: %f Mb, Number of allocation: %d\n", afterGCHeapAlloc, afterGCHeapObjects)
   445  	test.Assert(t, afterGCHeapAlloc < preHeapAlloc && afterGCHeapObjects < preHeapObjects)
   446  
   447  	// Trigger the finalizer of kclient be executed
   448  	time.Sleep(200 * time.Millisecond) // ensure the finalizer be executed
   449  	runtime.GC()
   450  	runtime.ReadMemStats(&ms)
   451  	secondGCHeapAlloc, secondGCHeapObjects := mb(ms.HeapAlloc), ms.HeapObjects
   452  	t.Logf("After second GC, allocation: %f Mb, Number of allocation: %d\n", secondGCHeapAlloc, secondGCHeapObjects)
   453  	test.Assert(t, secondGCHeapAlloc/2 < afterGCHeapAlloc && secondGCHeapObjects/2 < afterGCHeapObjects)
   454  }
   455  
   456  func TestMapThriftGenericClientFinalizer(t *testing.T) {
   457  	debug.SetGCPercent(-1)
   458  	defer debug.SetGCPercent(100)
   459  
   460  	var ms runtime.MemStats
   461  	runtime.ReadMemStats(&ms)
   462  	t.Logf("Before new clients, allocation: %f Mb, Number of allocation: %d\n", mb(ms.HeapAlloc), ms.HeapObjects)
   463  
   464  	clientCnt := 1000
   465  	clis := make([]genericclient.Client, clientCnt)
   466  	for i := 0; i < clientCnt; i++ {
   467  		p, err := generic.NewThriftFileProvider("./idl/mock.thrift")
   468  		test.Assert(t, err == nil, "generic NewThriftFileProvider failed, err=%v", err)
   469  		g, err := generic.MapThriftGeneric(p)
   470  		test.Assert(t, err == nil, "generic MapThriftGeneric failed, err=%v", err)
   471  		clis[i] = newGenericClient("destServiceName", g, "127.0.0.1:9021")
   472  	}
   473  
   474  	runtime.ReadMemStats(&ms)
   475  	t.Logf("After new clients, allocation: %f Mb, Number of allocation: %d\n", mb(ms.HeapAlloc), ms.HeapObjects)
   476  
   477  	runtime.GC()
   478  	runtime.ReadMemStats(&ms)
   479  	firstGCHeapAlloc, firstGCHeapObjects := mb(ms.HeapAlloc), ms.HeapObjects
   480  	t.Logf("After first GC, allocation: %f Mb, Number of allocation: %d\n", firstGCHeapAlloc, firstGCHeapObjects)
   481  
   482  	// Trigger the finalizer of generic client be executed
   483  	time.Sleep(200 * time.Millisecond) // ensure the finalizer be executed
   484  	runtime.GC()
   485  	runtime.ReadMemStats(&ms)
   486  	secondGCHeapAlloc, secondGCHeapObjects := mb(ms.HeapAlloc), ms.HeapObjects
   487  	t.Logf("After second GC, allocation: %f Mb, Number of allocation: %d\n", secondGCHeapAlloc, secondGCHeapObjects)
   488  	// test.Assert(t, secondGCHeapAlloc < firstGCHeapAlloc && secondGCHeapObjects < firstGCHeapObjects)
   489  
   490  	// Trigger the finalizer of kClient be executed
   491  	time.Sleep(200 * time.Millisecond) // ensure the finalizer be executed
   492  	runtime.GC()
   493  	runtime.ReadMemStats(&ms)
   494  	thirdGCHeapAlloc, thirdGCHeapObjects := mb(ms.HeapAlloc), ms.HeapObjects
   495  	t.Logf("After third GC, allocation: %f Mb, Number of allocation: %d\n", thirdGCHeapAlloc, thirdGCHeapObjects)
   496  	test.Assert(t, thirdGCHeapAlloc < secondGCHeapAlloc/2 && thirdGCHeapObjects < secondGCHeapObjects/2)
   497  }
   498  
   499  func mb(byteSize uint64) float32 {
   500  	return float32(byteSize) / float32(1024*1024)
   501  }