go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/proto/msgpackpb/generic_test.go (about) 1 // Copyright 2022 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package msgpackpb 16 17 import ( 18 "bytes" 19 "math" 20 "reflect" 21 "testing" 22 "time" 23 24 . "github.com/smartystreets/goconvey/convey" 25 "github.com/vmihailenco/msgpack/v5" 26 . "go.chromium.org/luci/common/testing/assertions" 27 "google.golang.org/protobuf/proto" 28 "google.golang.org/protobuf/reflect/protoreflect" 29 "google.golang.org/protobuf/types/known/durationpb" 30 ) 31 32 func TestRoundtrip(t *testing.T) { 33 t.Parallel() 34 35 testCases := []struct { 36 name string 37 input *TestMessage 38 err string 39 raw []byte 40 options []Option 41 }{ 42 { 43 name: "scalar fields", 44 input: &TestMessage{ 45 Boolval: true, 46 Intval: -100, 47 Uintval: 100, 48 ShortIntval: -50, 49 ShortUintval: 50, 50 Floatval: 6.28318531, 51 ShortFloatval: 3.1415, 52 Strval: "hi", 53 Value: VALUE_ONE, 54 }, 55 raw: []byte{ 56 137, // 9 element map 57 2, 195, // tag 2, true 58 3, 208, 156, // tag 3, -100 59 4, 100, // tag 4, 100 60 5, 208, 206, // tag 5, -50 61 6, 50, // tag 6, 50 62 7, 162, 104, 105, // tag 7, "hi" 63 8, 203, 64, 25, 33, 251, 84, 116, 161, 104, // tag 8, 6.28318531 64 9, 202, 64, 73, 14, 86, // tag 9, 3.1415 65 10, 1}, // tag 10, 1 66 options: []Option{Deterministic}, 67 }, 68 69 { 70 name: "repeated simple", 71 input: &TestMessage{ 72 Strings: []string{"hello", "there"}, 73 }, 74 raw: []byte{ 75 129, // 1 element map 76 13, 146, // tag 13, 2 element array 77 165, 104, 101, 108, 108, 111, // "hello" 78 165, 116, 104, 101, 114, 101, // "there" 79 }, 80 options: []Option{Deterministic}, 81 }, 82 83 { 84 name: "embedded message", 85 input: &TestMessage{ 86 SingleRecurse: &TestMessage{ 87 SingleRecurse: &TestMessage{ 88 Strval: "hello", 89 }, 90 }, 91 }, 92 raw: []byte{ 93 129, // 1 element map 94 14, // tag 13 95 129, // 1 element map 96 14, // tag 13 97 129, // 1 element map 98 7, 165, 104, 101, 108, 108, 111, // tag 7, "hello" 99 }, 100 options: []Option{Deterministic}, 101 }, 102 103 { 104 name: "external message", 105 input: &TestMessage{ 106 Duration: &durationpb.Duration{ 107 Seconds: 10000, 108 Nanos: 10000, 109 }, 110 }, 111 raw: []byte{ 112 129, // 1 element map 113 12, 146, // tag 12, 2 element ARRAY, since this message is encoded like a lua 'array' 114 205, 39, 16, // (implicit tag 1), 10000 115 205, 39, 16, // (implicit tag 2), 10000 116 }, 117 options: []Option{Deterministic}, 118 }, 119 120 { 121 name: "map", 122 input: &TestMessage{ 123 Mapfield: map[string]*TestMessage{ 124 "hello": {Strval: "there"}, 125 "general": {Strval: "kinobi..."}, 126 }, 127 }, 128 raw: []byte{ 129 129, // 1 element map 130 11, 130, // tag 11, 2 entry map 131 167, 103, 101, 110, 101, 114, 97, 108, // "general" 132 129, // 2 element map 133 7, 169, 107, 105, 110, 111, 98, 105, 46, 46, 46, // tag 7, "kenobi..." 134 165, 104, 101, 108, 108, 111, // "hello" 135 129, // 1 element map 136 7, 165, 116, 104, 101, 114, 101, // tag 7, "there" 137 }, 138 options: []Option{Deterministic}, 139 }, 140 141 { 142 name: "intern", 143 input: &TestMessage{ 144 Strval: "am interned", 145 Mapfield: map[string]*TestMessage{ 146 "another": {Boolval: true}, 147 "not": {Boolval: false}, 148 }, 149 SingleRecurse: &TestMessage{ 150 Strval: "also not", 151 }, 152 }, 153 raw: []byte{ 154 131, // 3 element map 155 7, 0, // tag 7, interned string 0 156 11, 130, // tag 11, 2 element map 157 1, 129, 2, 195, // interned string 1, 1 element map, tag 2, true 158 163, 110, 111, 116, 128, // "not", zero element map. 159 14, 129, // tag 14, 1 element map 160 7, 168, 97, 108, 115, 111, 32, 110, 111, 116, // tag 7, "also not" 161 }, 162 options: []Option{Deterministic, WithStringInternTable([]string{ 163 "am interned", 164 "another", 165 })}, 166 }, 167 } 168 169 Convey(`TestRoundtrip`, t, func() { 170 for _, tc := range testCases { 171 tc := tc 172 Convey(tc.name, func() { 173 raw, err := Marshal(tc.input, tc.options...) 174 if tc.err == "" { 175 So(err, ShouldBeNil) 176 } else { 177 So(err, ShouldErrLike, tc.err) 178 return 179 } 180 181 if tc.raw != nil { 182 So([]byte(raw), ShouldResemble, tc.raw) 183 } 184 185 msg := &TestMessage{} 186 So(Unmarshal(raw, msg, tc.options...), ShouldBeNil) 187 188 So(msg, ShouldResembleProto, tc.input) 189 }) 190 } 191 }) 192 193 } 194 195 func TestEncode(t *testing.T) { 196 t.Parallel() 197 198 Convey(`TestEncode`, t, func() { 199 Convey(`unknown fields`, func() { 200 // use Duration which encodes seconds with field 1, which is reserved. 201 enc, err := proto.Marshal(durationpb.New(20 * time.Second)) 202 So(err, ShouldBeNil) 203 204 tm := &TestMessage{} 205 So(proto.Unmarshal(enc, tm), ShouldBeNil) 206 207 So(tm.ProtoReflect().GetUnknown(), ShouldNotBeEmpty) 208 209 _, err = Marshal(tm) 210 So(err, ShouldErrLike, "unknown non-msgpack fields") 211 }) 212 }) 213 } 214 215 // TestDecode tests the pathway from msgpack -> proto, focusing on pathways 216 // where the msgpack message contains a different encoded value than the target 217 // field. 218 func TestDecode(t *testing.T) { 219 t.Parallel() 220 221 testCases := []struct { 222 name string 223 tweakEnc func(*msgpack.Encoder) 224 input any // will be encoded verbatim with 225 expect *TestMessage 226 expectUnknown protoreflect.RawFields 227 expectRaw msgpack.RawMessage 228 expectDecoded any 229 err string 230 }{ 231 { 232 name: "int32->int64", 233 input: map[int32]any{ 234 3: int32(10), 235 }, 236 expect: &TestMessage{Intval: 10}, 237 }, 238 { 239 name: "int8->int64", 240 input: map[int32]any{ 241 3: int8(10), 242 }, 243 expect: &TestMessage{Intval: 10}, 244 }, 245 { 246 name: "int64->int32", 247 input: map[int32]any{ 248 5: int64(10), 249 }, 250 expect: &TestMessage{ShortIntval: 10}, 251 }, 252 { 253 name: "int64->int32 (overflow)", 254 input: map[int32]any{ 255 5: int64(math.MaxInt32 * 2), 256 }, 257 expect: &TestMessage{ShortIntval: -2}, 258 }, 259 { 260 name: "float64->int32", 261 input: map[int32]any{ 262 5: float64(217), 263 }, 264 err: "bad type: expected int32, got float64", 265 }, 266 267 { 268 name: "unknown field", 269 input: map[int32]any{ 270 777: "nerds", 271 3: 100, 272 }, 273 expect: &TestMessage{ 274 Intval: 100, 275 }, 276 expectUnknown: []byte{ 277 250, 255, 255, 255, 15, // proto: 536870911: LEN 278 10, // proto: 10 bytes in this field 279 129, // msgpack: 1 element map 280 205, 3, 9, // msgpack: 777 281 165, 110, 101, 114, 100, 115, // msgpack: 5-char string, "nerds" 282 }, 283 expectRaw: []byte{ 284 130, // 2 item map 285 3, 100, // tag 3, 100 286 205, 3, 9, 165, 110, 101, 114, 100, 115, // tag 777, 5 char string "nerds" 287 }, 288 expectDecoded: map[int32]any{ 289 3: int64(100), 290 777: "nerds", 291 }, 292 }, 293 294 { 295 name: "sparse array", 296 input: map[int32]any{ 297 13: map[int32]string{ 298 3: "hello", 299 12: "there", 300 }, 301 }, 302 expect: &TestMessage{ 303 Strings: []string{ 304 "", "", "", 305 "hello", 306 "", "", "", 307 "", "", "", 308 "", "", 309 "there", 310 }, 311 }, 312 }, 313 } 314 315 Convey(`TestDecode`, t, func() { 316 for _, tc := range testCases { 317 tc := tc 318 Convey(tc.name, func() { 319 enc := msgpack.GetEncoder() 320 defer msgpack.PutEncoder(enc) 321 322 buf := bytes.Buffer{} 323 enc.Reset(&buf) 324 if tc.tweakEnc != nil { 325 tc.tweakEnc(enc) 326 } 327 So(enc.Encode(tc.input), ShouldBeNil) 328 329 msg := &TestMessage{} 330 err := Unmarshal(buf.Bytes(), msg) 331 if tc.err == "" { 332 So(err, ShouldBeNil) 333 334 known := proto.Clone(msg).(*TestMessage) 335 known.ProtoReflect().SetUnknown(nil) 336 So(known, ShouldResembleProto, tc.expect) 337 338 So(msg.ProtoReflect().GetUnknown(), ShouldResemble, tc.expectUnknown) 339 340 if tc.expectRaw != nil { 341 raw, err := Marshal(msg, Deterministic) 342 So(err, ShouldBeNil) 343 344 So(raw, ShouldResemble, tc.expectRaw) 345 346 if len(msg.ProtoReflect().GetUnknown()) > 0 { 347 dec := msgpack.GetDecoder() 348 defer msgpack.PutDecoder(dec) 349 dec.Reset(bytes.NewBuffer(raw)) 350 dec.UseLooseInterfaceDecoding(true) 351 dec.SetMapDecoder(func(d *msgpack.Decoder) (any, error) { 352 return d.DecodeUntypedMap() 353 }) 354 355 decoded := reflect.MakeMap(reflect.TypeOf(tc.expectDecoded)) 356 357 So(dec.DecodeValue(decoded), ShouldBeNil) 358 359 So(decoded.Interface(), ShouldResemble, tc.expectDecoded) 360 } 361 } 362 } else { 363 So(err, ShouldErrLike, tc.err) 364 } 365 366 }) 367 } 368 }) 369 370 }