github.com/jhump/protoreflect@v1.16.0/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/jhump/protoreflect/desc" 13 "github.com/jhump/protoreflect/internal/testprotos" 14 "github.com/jhump/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 }