github.com/cloudwego/dynamicgo@v0.2.6-0.20240519101509-707f41b6b834/conv/p2j/conv_test.go (about)

     1  package p2j
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"math"
     9  	"os"
    10  	"runtime"
    11  	"runtime/debug"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/cloudwego/dynamicgo/conv"
    16  	"github.com/cloudwego/dynamicgo/internal/util_test"
    17  	"github.com/cloudwego/dynamicgo/meta"
    18  	"github.com/cloudwego/dynamicgo/proto"
    19  	"github.com/cloudwego/dynamicgo/testdata/kitex_gen/pb/base"
    20  	"github.com/cloudwego/dynamicgo/testdata/kitex_gen/pb/example2"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  	goprotowire "google.golang.org/protobuf/encoding/protowire"
    24  	goproto "google.golang.org/protobuf/proto"
    25  )
    26  
    27  var (
    28  	debugAsyncGC = os.Getenv("SONIC_NO_ASYNC_GC") == ""
    29  )
    30  
    31  func TestMain(m *testing.M) {
    32  	go func() {
    33  		if !debugAsyncGC {
    34  			return
    35  		}
    36  		println("Begin GC looping...")
    37  		for {
    38  			runtime.GC()
    39  			debug.FreeOSMemory()
    40  		}
    41  	}()
    42  	time.Sleep(time.Millisecond)
    43  	m.Run()
    44  }
    45  
    46  const (
    47  	exampleIDLPath   = "testdata/idl/example2.proto"
    48  	exampleProtoPath = "testdata/data/example3_pb.bin"
    49  	exampleJSONPath  = "testdata/data/example3req.json"
    50  )
    51  
    52  func TestBuildData(t *testing.T) {
    53  	if err := saveExampleReqProtoBufData(); err != nil {
    54  		panic("build example3ProtoData failed")
    55  	}
    56  }
    57  
    58  func TestConvProto3JSON(t *testing.T) {
    59  	includeDirs := util_test.MustGitPath("testdata/idl/") // includeDirs is used to find the include files.
    60  	messageDesc := proto.FnRequest(proto.GetFnDescFromFile(exampleIDLPath, "ExampleMethod", proto.Options{}, includeDirs))
    61  	//js := getExample2JSON()
    62  	cv := NewBinaryConv(conv.Options{})
    63  	in := readExampleReqProtoBufData()
    64  	out, err := cv.Do(context.Background(), messageDesc, in)
    65  	if err != nil {
    66  		t.Fatal(err)
    67  	}
    68  	exp := example2.ExampleReq{}
    69  	// use kitex_util to check proto data validity
    70  	l := 0
    71  	dataLen := len(in)
    72  	for l < dataLen {
    73  		id, wtyp, tagLen := goprotowire.ConsumeTag(in)
    74  		if tagLen < 0 {
    75  			t.Fatal("proto data error format")
    76  		}
    77  		l += tagLen
    78  		in = in[tagLen:]
    79  		offset, err := exp.FastRead(in, int8(wtyp), int32(id))
    80  		require.Nil(t, err)
    81  		in = in[offset:]
    82  		l += offset
    83  	}
    84  	if len(in) != 0 {
    85  		t.Fatal("proto data error format")
    86  	}
    87  	// check json data validity, convert it into act struct
    88  	var act example2.ExampleReq
    89  	require.Nil(t, json.Unmarshal([]byte(out), &act))
    90  	assert.Equal(t, exp, act)
    91  }
    92  
    93  // construct ExampleReq Object
    94  func constructExampleReqObject() *example2.ExampleReq {
    95  	req := example2.ExampleReq{}
    96  	req.Msg = "hello"
    97  	req.Subfix = math.MaxFloat64
    98  	req.InnerBase2 = &example2.InnerBase2{}
    99  	req.InnerBase2.Bool = true
   100  	req.InnerBase2.Uint32 = uint32(123)
   101  	req.InnerBase2.Uint64 = uint64(123)
   102  	req.InnerBase2.Double = float64(22.3)
   103  	req.InnerBase2.String_ = "hello_inner"
   104  	req.InnerBase2.ListInt32 = []int32{12, 13, 14, 15, 16, 17}
   105  	req.InnerBase2.MapStringString = map[string]string{"m1": "aaa", "m2": "bbb", "m3": "ccc", "m4": "ddd"}
   106  	req.InnerBase2.SetInt32 = []int32{200, 201, 202, 203, 204, 205}
   107  	req.InnerBase2.Foo = example2.FOO_FOO_A
   108  	req.InnerBase2.MapInt32String = map[int32]string{1: "aaa", 2: "bbb", 3: "ccc", 4: "ddd"}
   109  	req.InnerBase2.Binary = []byte{0x1, 0x2, 0x3, 0x4}
   110  	req.InnerBase2.MapUint32String = map[uint32]string{uint32(1): "u32aa", uint32(2): "u32bb", uint32(3): "u32cc", uint32(4): "u32dd"}
   111  	req.InnerBase2.MapUint64String = map[uint64]string{uint64(1): "u64aa", uint64(2): "u64bb", uint64(3): "u64cc", uint64(4): "u64dd"}
   112  	req.InnerBase2.MapInt64String = map[int64]string{int64(1): "64aaa", int64(2): "64bbb", int64(3): "64ccc", int64(4): "64ddd"}
   113  	req.InnerBase2.ListString = []string{"111", "222", "333", "44", "51", "6"}
   114  	req.InnerBase2.ListBase = []*base.Base{{
   115  		LogID:  "logId",
   116  		Caller: "caller",
   117  		Addr:   "addr",
   118  		Client: "client",
   119  		TrafficEnv: &base.TrafficEnv{
   120  			Open: false,
   121  			Env:  "env",
   122  		},
   123  		Extra: map[string]string{"1a": "aaa", "2a": "bbb", "3a": "ccc", "4a": "ddd"},
   124  	}, {
   125  		LogID:  "logId2",
   126  		Caller: "caller2",
   127  		Addr:   "addr2",
   128  		Client: "client2",
   129  		TrafficEnv: &base.TrafficEnv{
   130  			Open: true,
   131  			Env:  "env2",
   132  		},
   133  		Extra: map[string]string{"1a": "aaa2", "2a": "bbb2", "3a": "ccc2", "4a": "ddd2"},
   134  	}}
   135  	req.InnerBase2.MapInt64Base = map[int64]*base.Base{int64(1): {
   136  		LogID:  "logId",
   137  		Caller: "caller",
   138  		Addr:   "addr",
   139  		Client: "client",
   140  		TrafficEnv: &base.TrafficEnv{
   141  			Open: false,
   142  			Env:  "env",
   143  		},
   144  		Extra: map[string]string{"1a": "aaa", "2a": "bbb", "3a": "ccc", "4a": "ddd"},
   145  	}, int64(2): {
   146  		LogID:  "logId2",
   147  		Caller: "caller2",
   148  		Addr:   "addr2",
   149  		Client: "client2",
   150  		TrafficEnv: &base.TrafficEnv{
   151  			Open: true,
   152  			Env:  "env2",
   153  		},
   154  		Extra: map[string]string{"1a": "aaa2", "2a": "bbb2", "3a": "ccc2", "4a": "ddd2"},
   155  	}}
   156  	req.InnerBase2.MapStringBase = map[string]*base.Base{"1": {
   157  		LogID:  "logId",
   158  		Caller: "caller",
   159  		Addr:   "addr",
   160  		Client: "client",
   161  		TrafficEnv: &base.TrafficEnv{
   162  			Open: false,
   163  			Env:  "env",
   164  		},
   165  		Extra: map[string]string{"1a": "aaa", "2a": "bbb", "3a": "ccc", "4a": "ddd"},
   166  	}, "2": {
   167  		LogID:  "logId2",
   168  		Caller: "caller2",
   169  		Addr:   "addr2",
   170  		Client: "client2",
   171  		TrafficEnv: &base.TrafficEnv{
   172  			Open: true,
   173  			Env:  "env2",
   174  		},
   175  		Extra: map[string]string{"1a": "aaa2", "2a": "bbb2", "3a": "ccc2", "4a": "ddd2"},
   176  	}}
   177  	req.InnerBase2.Base = &base.Base{}
   178  	req.InnerBase2.Base.LogID = "logId"
   179  	req.InnerBase2.Base.Caller = "caller"
   180  	req.InnerBase2.Base.Addr = "addr"
   181  	req.InnerBase2.Base.Client = "client"
   182  	req.InnerBase2.Base.TrafficEnv = &base.TrafficEnv{}
   183  	req.InnerBase2.Base.TrafficEnv.Open = false
   184  	req.InnerBase2.Base.TrafficEnv.Env = "env"
   185  	req.InnerBase2.Base.Extra = map[string]string{"1b": "aaa", "2b": "bbb", "3b": "ccc", "4b": "ddd"}
   186  	return &req
   187  }
   188  
   189  // marshal ExampleReq Object to ProtoBinary, and write binaryData into exampleProtoPath
   190  func saveExampleReqProtoBufData() error {
   191  	req := constructExampleReqObject()
   192  	data, err := goproto.Marshal(req.ProtoReflect().Interface())
   193  	if err != nil {
   194  		panic("goproto marshal data failed")
   195  	}
   196  	checkExist := func(path string) bool {
   197  		_, err := os.Stat(path)
   198  		if err != nil {
   199  			if os.IsExist(err) {
   200  				return true
   201  			}
   202  			return false
   203  		}
   204  		return true
   205  	}
   206  	var file *os.File
   207  	absoluteExampleProtoPath := util_test.MustGitPath(exampleProtoPath)
   208  	if checkExist(absoluteExampleProtoPath) == true {
   209  		if err := os.Remove(absoluteExampleProtoPath); err != nil {
   210  			panic("delete protoBinaryFile failed")
   211  		}
   212  	}
   213  	file, err = os.Create(absoluteExampleProtoPath)
   214  	if err != nil {
   215  		panic("create protoBinaryFile failed")
   216  	}
   217  	defer file.Close()
   218  	if _, err := file.Write(data); err != nil {
   219  		panic("write protoBinary data failed")
   220  	}
   221  	return nil
   222  }
   223  
   224  // read ProtoBuf's data in binary format from exampleProtoPath
   225  func readExampleReqProtoBufData() []byte {
   226  	out, err := ioutil.ReadFile(util_test.MustGitPath(exampleProtoPath))
   227  	if err != nil {
   228  		panic(err)
   229  	}
   230  	return out
   231  }
   232  
   233  // marshal ExampleReq Object to JsonBinary, and write binaryData into exampleJSONPath
   234  func saveExampleReqJSONData() error {
   235  	req := constructExampleReqObject()
   236  	data, err := json.Marshal(req)
   237  	if err != nil {
   238  		panic(fmt.Sprintf("buildExampleJSONData failed, err: %v", err.Error()))
   239  	}
   240  	checkExist := func(path string) bool {
   241  		_, err := os.Stat(path)
   242  		if err != nil {
   243  			if os.IsExist(err) {
   244  				return true
   245  			}
   246  			return false
   247  		}
   248  		return true
   249  	}
   250  	var file *os.File
   251  	absoluteExampleJSONPath := util_test.MustGitPath(exampleJSONPath)
   252  	if checkExist(absoluteExampleJSONPath) == true {
   253  		if err := os.Remove(absoluteExampleJSONPath); err != nil {
   254  			panic("delete protoBinaryFile failed")
   255  		}
   256  	}
   257  	file, err = os.Create(absoluteExampleJSONPath)
   258  	if err != nil {
   259  		panic("create protoBinaryFile failed")
   260  	}
   261  	defer file.Close()
   262  	if _, err := file.WriteString(string(data)); err != nil {
   263  		panic("write protoJSONData failed")
   264  	}
   265  	return nil
   266  }
   267  
   268  // read JSON's data in binary format from exampleJSONPath
   269  func readExampleReqJSONData() string {
   270  	out, err := ioutil.ReadFile(util_test.MustGitPath(exampleJSONPath))
   271  	if err != nil {
   272  		panic(err)
   273  	}
   274  	return string(out)
   275  }
   276  
   277  func getExampleInt2Float() *proto.TypeDescriptor {
   278  	includeDirs := util_test.MustGitPath("testdata/idl/") // includeDirs is used to find the include files.
   279  	svc, err := proto.NewDescritorFromPath(context.Background(), util_test.MustGitPath(exampleIDLPath), includeDirs)
   280  	if err != nil {
   281  		panic(err)
   282  	}
   283  	return (*svc).LookupMethodByName("Int2FloatMethod").Output()
   284  }
   285  
   286  func TestInt2String(t *testing.T) {
   287  	cv := NewBinaryConv(conv.Options{})
   288  	desc := getExampleInt2Float()
   289  	exp := example2.ExampleInt2Float{}
   290  	exp.Int32 = 1
   291  	exp.Int64 = 2
   292  	exp.Float64 = 3.14
   293  	exp.String_ = "hello"
   294  	exp.Subfix = 0.92653
   295  	ctx := context.Background()
   296  	in := make([]byte, exp.Size())
   297  	exp.FastWrite(in)
   298  
   299  	out, err := cv.Do(ctx, desc, in)
   300  	require.NoError(t, err)
   301  	require.Equal(t, `{"Int32":1,"Float64":3.14,"String":"hello","Int64":2,"Subfix":0.92653}`, string(out))
   302  
   303  	cv.opts.EnableValueMapping = false
   304  	out, err = cv.Do(ctx, desc, in)
   305  	require.NoError(t, err)
   306  	require.Equal(t, (`{"Int32":1,"Float64":3.14,"String":"hello","Int64":2,"Subfix":0.92653}`), string(out))
   307  
   308  	cv.opts.String2Int64 = true
   309  	out, err = cv.Do(ctx, desc, in)
   310  	require.NoError(t, err)
   311  	require.Equal(t, (`{"Int32":1,"Float64":3.14,"String":"hello","Int64":"2","Subfix":0.92653}`), string(out))
   312  }
   313  
   314  func getExampleReqPartialDesc() *proto.TypeDescriptor {
   315  	includeDirs := util_test.MustGitPath("testdata/idl/") // includeDirs is used to find the include files.
   316  	return proto.FnRequest(proto.GetFnDescFromFile(exampleIDLPath, "ExamplePartialMethod", proto.Options{}, includeDirs))
   317  }
   318  
   319  func getExampleRespPartialDesc() *proto.TypeDescriptor {
   320  	includeDirs := util_test.MustGitPath("testdata/idl/") // includeDirs is used to find the include files.
   321  	return proto.FnResponse(proto.GetFnDescFromFile(exampleIDLPath, "ExamplePartialMethod2", proto.Options{}, includeDirs))
   322  }
   323  
   324  // construct ExampleResp Object
   325  func getExampleResp() *example2.ExampleResp {
   326  	resp := example2.ExampleResp{}
   327  	resp.Msg = "messagefist"
   328  	resp.RequiredField = "hello"
   329  	resp.BaseResp = &base.BaseResp{}
   330  	resp.BaseResp.StatusMessage = "status1"
   331  	resp.BaseResp.StatusCode = 32
   332  	resp.BaseResp.Extra = map[string]string{"1b": "aaa", "2b": "bbb", "3b": "ccc", "4b": "ddd"}
   333  	return &resp
   334  }
   335  
   336  func TestUnknowFields(t *testing.T) {
   337  	t.Run("top", func(t *testing.T) {
   338  		cv := NewBinaryConv(conv.Options{
   339  			DisallowUnknownField: true,
   340  		})
   341  		resp := getExampleResp()
   342  		data, err := goproto.Marshal(resp.ProtoReflect().Interface())
   343  		if err != nil {
   344  			t.Fatal("marshal protobuf data failed")
   345  		}
   346  		partialRespDesc := getExampleRespPartialDesc()
   347  		_, err = cv.Do(context.Background(), partialRespDesc, data)
   348  		require.Error(t, err)
   349  		require.Equal(t, meta.ErrUnknownField, err.(meta.Error).Code.Behavior())
   350  	})
   351  
   352  	t.Run("nested", func(t *testing.T) {
   353  		cv := NewBinaryConv(conv.Options{
   354  			DisallowUnknownField: true,
   355  		})
   356  		partialReqDesc := getExampleReqPartialDesc()
   357  		in := readExampleReqProtoBufData()
   358  		_, err := cv.Do(context.Background(), partialReqDesc, in)
   359  		require.Error(t, err)
   360  		require.Equal(t, meta.ErrUnknownField, err.(meta.Error).Code.Behavior())
   361  	})
   362  
   363  	t.Run("skip top", func(t *testing.T) {
   364  		cv := NewBinaryConv(conv.Options{
   365  			DisallowUnknownField: false,
   366  		})
   367  		resp := getExampleResp()
   368  		data, err := goproto.Marshal(resp.ProtoReflect().Interface())
   369  		if err != nil {
   370  			t.Fatal("marshal protobuf data failed")
   371  		}
   372  		partialRespDesc := getExampleRespPartialDesc()
   373  		_, err = cv.Do(context.Background(), partialRespDesc, data)
   374  		require.NoError(t, err)
   375  	})
   376  
   377  	t.Run("skip nested", func(t *testing.T) {
   378  		cv := NewBinaryConv(conv.Options{
   379  			DisallowUnknownField: false,
   380  		})
   381  		partialReqDesc := getExampleReqPartialDesc()
   382  		in := readExampleReqProtoBufData()
   383  		_, err := cv.Do(context.Background(), partialReqDesc, in)
   384  		require.NoError(t, err)
   385  	})
   386  }