github.com/bakjos/protoreflect@v1.9.2/dynamic/marshal_test.go (about)

     1  package dynamic
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"reflect"
     7  	"testing"
     8  
     9  	"github.com/golang/protobuf/protoc-gen-go/descriptor"
    10  
    11  	"github.com/golang/protobuf/proto"
    12  
    13  	"github.com/bakjos/protoreflect/desc"
    14  	"github.com/bakjos/protoreflect/internal/testprotos"
    15  	"github.com/bakjos/protoreflect/internal/testutil"
    16  )
    17  
    18  // Shared stuff for marshalling and unmarshalling tests. This is used for the binary format, the text
    19  // format, and the JSON format.
    20  
    21  var unaryFieldsPosMsg = &testprotos.UnaryFields{
    22  	I: proto.Int32(1),
    23  	J: proto.Int64(2),
    24  	K: proto.Int32(3),
    25  	L: proto.Int64(4),
    26  	M: proto.Uint32(5),
    27  	N: proto.Uint64(6),
    28  	O: proto.Uint32(7),
    29  	P: proto.Uint64(8),
    30  	Q: proto.Int32(9),
    31  	R: proto.Int64(10),
    32  	S: proto.Float32(11),
    33  	T: proto.Float64(12),
    34  	U: []byte{0, 1, 2, 3, 4, 5, 6, 7},
    35  	V: proto.String("foobar"),
    36  	W: proto.Bool(true),
    37  	X: &testprotos.RepeatedFields{
    38  		I: []int32{3},
    39  		V: []string{"baz"},
    40  	},
    41  	Groupy: &testprotos.UnaryFields_GroupY{
    42  		Ya: proto.String("bedazzle"),
    43  		Yb: proto.Int32(42),
    44  	},
    45  	Z: testprotos.TestEnum_SECOND.Enum(),
    46  }
    47  
    48  var unaryFieldsNegMsg = &testprotos.UnaryFields{
    49  	I: proto.Int32(-1),
    50  	J: proto.Int64(-2),
    51  	K: proto.Int32(-3),
    52  	L: proto.Int64(-4),
    53  	M: proto.Uint32(5),
    54  	N: proto.Uint64(6),
    55  	O: proto.Uint32(7),
    56  	P: proto.Uint64(8),
    57  	Q: proto.Int32(-9),
    58  	R: proto.Int64(-10),
    59  	S: proto.Float32(-11),
    60  	T: proto.Float64(-12),
    61  	U: []byte{0, 1, 2, 3, 4, 5, 6, 7},
    62  	V: proto.String("foobar"),
    63  	W: proto.Bool(true),
    64  	X: &testprotos.RepeatedFields{
    65  		I: []int32{-3},
    66  		V: []string{"baz"},
    67  	},
    68  	Groupy: &testprotos.UnaryFields_GroupY{
    69  		Ya: proto.String("bedazzle"),
    70  		Yb: proto.Int32(-42),
    71  	},
    72  	Z: testprotos.TestEnum_SECOND.Enum(),
    73  }
    74  
    75  var unaryFieldsPosInfMsg = &testprotos.UnaryFields{
    76  	S: proto.Float32(float32(math.Inf(1))),
    77  	T: proto.Float64(math.Inf(1)),
    78  }
    79  
    80  var unaryFieldsNegInfMsg = &testprotos.UnaryFields{
    81  	S: proto.Float32(float32(math.Inf(-1))),
    82  	T: proto.Float64(math.Inf(-1)),
    83  }
    84  
    85  var unaryFieldsNanMsg = &testprotos.UnaryFields{
    86  	S: proto.Float32(float32(math.NaN())),
    87  	T: proto.Float64(math.NaN()),
    88  }
    89  
    90  var repeatedFieldsMsg = &testprotos.RepeatedFields{
    91  	I: []int32{1, -2, 3},
    92  	J: []int64{-4, 5, -6},
    93  	K: []int32{7, -8, 9},
    94  	L: []int64{-10, 11, -12},
    95  	M: []uint32{13, 14, 15},
    96  	N: []uint64{16, 17, 18},
    97  	O: []uint32{19, 20, 21},
    98  	P: []uint64{22, 23, 24},
    99  	Q: []int32{25, 26, 27},
   100  	R: []int64{28, 29, 30},
   101  	S: []float32{31, 32, 33},
   102  	T: []float64{34, 35, 36},
   103  	U: [][]byte{{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}},
   104  	V: []string{"foo", "bar", "baz"},
   105  	W: []bool{true, false, true},
   106  	X: []*testprotos.UnaryFields{
   107  		{I: proto.Int32(-32), V: proto.String("baz")},
   108  		{I: proto.Int32(-64), V: proto.String("bozo")},
   109  	},
   110  	Groupy: []*testprotos.RepeatedFields_GroupY{
   111  		{Ya: proto.String("bedazzle"), Yb: proto.Int32(42)},
   112  		{Ya: proto.String("buzzard"), Yb: proto.Int32(-421)},
   113  	},
   114  	Z: []testprotos.TestEnum{testprotos.TestEnum_SECOND, testprotos.TestEnum_THIRD, testprotos.TestEnum_FIRST},
   115  }
   116  
   117  var repeatedFieldsInfNanMsg = &testprotos.RepeatedFields{
   118  	S: []float32{float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN())},
   119  	T: []float64{math.Inf(1), math.Inf(-1), math.NaN()},
   120  }
   121  
   122  var repeatedPackedFieldsMsg = &testprotos.RepeatedPackedFields{
   123  	I: []int32{1, -2, 3},
   124  	J: []int64{-4, 5, -6},
   125  	K: []int32{7, -8, 9},
   126  	L: []int64{-10, 11, -12},
   127  	M: []uint32{13, 14, 15},
   128  	N: []uint64{16, 17, 18},
   129  	O: []uint32{19, 20, 21},
   130  	P: []uint64{22, 23, 24},
   131  	Q: []int32{25, 26, 27},
   132  	R: []int64{28, 29, 30},
   133  	S: []float32{31, 32, 33},
   134  	T: []float64{34, 35, 36},
   135  	U: []bool{true, false, true},
   136  	Groupy: []*testprotos.RepeatedPackedFields_GroupY{
   137  		{Yb: []int32{42, 84, 126, 168, 210}},
   138  		{Yb: []int32{-210, -168, -126, -84, -42}},
   139  	},
   140  	V: []testprotos.TestEnum{testprotos.TestEnum_SECOND, testprotos.TestEnum_THIRD, testprotos.TestEnum_FIRST},
   141  }
   142  
   143  var repeatedPackedFieldsInfNanMsg = &testprotos.RepeatedPackedFields{
   144  	S: []float32{float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN())},
   145  	T: []float64{math.Inf(1), math.Inf(-1), math.NaN()},
   146  }
   147  
   148  var mapKeyFieldsMsg = &testprotos.MapKeyFields{
   149  	I: map[int32]string{1: "foo", -2: "bar", 3: "baz"},
   150  	J: map[int64]string{-4: "foo", 5: "bar", -6: "baz"},
   151  	K: map[int32]string{7: "foo", -8: "bar", 9: "baz"},
   152  	L: map[int64]string{-10: "foo", 11: "bar", -12: "baz"},
   153  	M: map[uint32]string{13: "foo", 14: "bar", 15: "baz"},
   154  	N: map[uint64]string{16: "foo", 17: "bar", 18: "baz"},
   155  	O: map[uint32]string{19: "foo", 20: "bar", 21: "baz"},
   156  	P: map[uint64]string{22: "foo", 23: "bar", 24: "baz"},
   157  	Q: map[int32]string{25: "foo", 26: "bar", 27: "baz"},
   158  	R: map[int64]string{28: "foo", 29: "bar", 30: "baz"},
   159  	S: map[string]string{"a": "foo", "b": "bar", "c": "baz"},
   160  	T: map[bool]string{true: "foo", false: "bar"},
   161  }
   162  
   163  var mapValueFieldsMsg = &testprotos.MapValFields{
   164  	I: map[string]int32{"a": 1, "b": -2, "c": 3},
   165  	J: map[string]int64{"a": -4, "b": 5, "c": -6},
   166  	K: map[string]int32{"a": 7, "b": -8, "c": 9},
   167  	L: map[string]int64{"a": -10, "b": 11, "c": -12},
   168  	M: map[string]uint32{"a": 13, "b": 14, "c": 15},
   169  	N: map[string]uint64{"a": 16, "b": 17, "c": 18},
   170  	O: map[string]uint32{"a": 19, "b": 20, "c": 21},
   171  	P: map[string]uint64{"a": 22, "b": 23, "c": 24},
   172  	Q: map[string]int32{"a": 25, "b": 26, "c": 27},
   173  	R: map[string]int64{"a": 28, "b": 29, "c": 30},
   174  	S: map[string]float32{"a": 31, "b": 32, "c": 33},
   175  	T: map[string]float64{"a": 34, "b": 35, "c": 36},
   176  	U: map[string][]byte{"a": {0, 1, 2, 3}, "b": {4, 5, 6, 7}, "c": {8, 9, 10, 11}},
   177  	V: map[string]string{"a": "foo", "b": "bar", "c": "baz"},
   178  	W: map[string]bool{"a": true, "b": false, "c": true},
   179  	X: map[string]*testprotos.UnaryFields{
   180  		"a": {I: proto.Int32(-32), V: proto.String("baz")},
   181  		"b": {I: proto.Int32(-64), V: proto.String("bozo")},
   182  	},
   183  	Y: map[string]testprotos.TestEnum{"a": testprotos.TestEnum_SECOND, "b": testprotos.TestEnum_THIRD, "c": testprotos.TestEnum_FIRST},
   184  }
   185  
   186  var mapValueFieldsInfNanMsg = &testprotos.MapValFields{
   187  	S: map[string]float32{"a": float32(math.Inf(1)), "b": float32(math.Inf(-1)), "c": float32(math.NaN())},
   188  	T: map[string]float64{"a": math.Inf(1), "b": math.Inf(-1), "c": math.NaN()},
   189  }
   190  
   191  var mapValueFieldsNilMsg = &testprotos.TestRequest{
   192  	Others: map[string]*testprotos.TestMessage{"a": nil, "b": nil},
   193  }
   194  
   195  var mapValueFieldsNilUnknownMsg proto.Message
   196  var mdForUnknownMsg *desc.MessageDescriptor
   197  
   198  func init() {
   199  	// NB: can't use desc/builder package because that would cause dependency cycle :(
   200  	fdp := &descriptor.FileDescriptorProto{
   201  		Name:    proto.String("foo.proto"),
   202  		Syntax:  proto.String("proto3"),
   203  		Package: proto.String("example"),
   204  		MessageType: []*descriptor.DescriptorProto{
   205  			{
   206  				Name: proto.String("Message"),
   207  				Field: []*descriptor.FieldDescriptorProto{
   208  					{
   209  						Name:     proto.String("vals"),
   210  						Type:     descriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
   211  						Number:   proto.Int32(1),
   212  						Label:    descriptor.FieldDescriptorProto_LABEL_REPEATED.Enum(),
   213  						TypeName: proto.String(".example.Message.ValsEntry"),
   214  					},
   215  				},
   216  				NestedType: []*descriptor.DescriptorProto{
   217  					{
   218  						Name: proto.String("ValsEntry"),
   219  						Options: &descriptor.MessageOptions{
   220  							MapEntry: proto.Bool(true),
   221  						},
   222  						Field: []*descriptor.FieldDescriptorProto{
   223  							{
   224  								Name:   proto.String("key"),
   225  								Number: proto.Int32(1),
   226  								Type:   descriptor.FieldDescriptorProto_TYPE_STRING.Enum(),
   227  								Label:  descriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
   228  							},
   229  							{
   230  								Name:     proto.String("value"),
   231  								Number:   proto.Int32(2),
   232  								Type:     descriptor.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
   233  								Label:    descriptor.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
   234  								TypeName: proto.String(".example.Message"),
   235  							},
   236  						},
   237  					},
   238  				},
   239  			},
   240  		},
   241  	}
   242  	fd, err := desc.CreateFileDescriptor(fdp)
   243  	if err != nil {
   244  		panic(err)
   245  	}
   246  	mdForUnknownMsg = fd.GetMessageTypes()[0]
   247  	mapValueFieldsNilUnknownMsg = &unknownMsg{
   248  		Vals: map[string]*unknownMsg{"a": nil, "b": nil},
   249  	}
   250  }
   251  
   252  // This message looks and acts like a proto but is NOT in the registry, so proto.MessageType
   253  // returns nil, which forces us to fallback to a nil *dynamic.Message when representing a
   254  // nil map value in a dynamic message, allowing tests to check that strange edge case.
   255  type unknownMsg struct {
   256  	Vals map[string]*unknownMsg `protobuf:"bytes,1,rep,name=vals,proto3" json:"vals,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
   257  }
   258  
   259  func (m *unknownMsg) XXX_MessageName() string {
   260  	return "example.Message"
   261  }
   262  func (m *unknownMsg) Reset() {
   263  	m.Vals = nil
   264  }
   265  func (m *unknownMsg) String() string {
   266  	return fmt.Sprintf("%#v", m.Vals)
   267  }
   268  func (m *unknownMsg) ProtoMessage() {
   269  }
   270  func (m *unknownMsg) GetMessageDescriptor() *desc.MessageDescriptor {
   271  	return mdForUnknownMsg
   272  }
   273  
   274  func doTranslationParty(t *testing.T, msg proto.Message,
   275  	marshalPm func(proto.Message) ([]byte, error), unmarshalPm func([]byte, proto.Message) error,
   276  	marshalDm func(*Message) ([]byte, error), unmarshalDm func(*Message, []byte) error,
   277  	includesNaN, compareBytes, outputIsString bool) {
   278  
   279  	md, err := desc.LoadMessageDescriptorForMessage(msg)
   280  	testutil.Ok(t, err)
   281  	dm := NewMessage(md)
   282  
   283  	b, err := marshalPm(msg)
   284  	testutil.Ok(t, err)
   285  	err = unmarshalDm(dm, b)
   286  	testutil.Ok(t, err, "failed to unmarshal from: %s", b)
   287  
   288  	// both techniques to marshal do the same thing
   289  	b2a, err := marshalPm(dm)
   290  	testutil.Ok(t, err)
   291  	b2b, err := marshalDm(dm)
   292  	testutil.Ok(t, err)
   293  	testutil.Eq(t, b2a, b2b)
   294  
   295  	// round trip back to proto.Message
   296  	msg2 := reflect.New(reflect.TypeOf(msg).Elem()).Interface().(proto.Message)
   297  	err = unmarshalPm(b2a, msg2)
   298  	testutil.Ok(t, err, "failed to unmarshal from: %s", b2a)
   299  
   300  	if !includesNaN {
   301  		// NaN fields are never equal so this would always be false
   302  		testutil.Ceq(t, msg, msg2, eqpm)
   303  	}
   304  	if compareBytes {
   305  		if outputIsString {
   306  			testutil.Eq(t, string(b), string(b2a))
   307  		} else {
   308  			testutil.Eq(t, b, b2a)
   309  		}
   310  	}
   311  
   312  	// and back again
   313  	b3, err := marshalPm(msg2)
   314  	testutil.Ok(t, err)
   315  	dm2 := NewMessage(md)
   316  	err = unmarshalDm(dm2, b3)
   317  	testutil.Ok(t, err, "failed to unmarshal from: %s", b3)
   318  
   319  	if !includesNaN {
   320  		testutil.Ceq(t, dm, dm2, eqdm)
   321  	}
   322  
   323  	// dynamic message -> (bytes) -> dynamic message
   324  	// both techniques to unmarshal are equivalent
   325  	dm3 := NewMessage(md)
   326  	err = unmarshalPm(b2a, dm3)
   327  	testutil.Ok(t, err, "failed to unmarshal from: %s", b2a)
   328  	dm4 := NewMessage(md)
   329  	err = unmarshalDm(dm4, b2a)
   330  	testutil.Ok(t, err, "failed to unmarshal from: %s", b2a)
   331  
   332  	if !includesNaN {
   333  		testutil.Ceq(t, dm, dm3, eqdm)
   334  		testutil.Ceq(t, dm, dm4, eqdm)
   335  	}
   336  }