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