1// Go support for Protocol Buffers - Google's data interchange format 2// 3// Copyright 2010 The Go Authors. All rights reserved. 4// https://github.com/golang/protobuf 5// 6// Redistribution and use in source and binary forms, with or without 7// modification, are permitted provided that the following conditions are 8// met: 9// 10// * Redistributions of source code must retain the above copyright 11// notice, this list of conditions and the following disclaimer. 12// * Redistributions in binary form must reproduce the above 13// copyright notice, this list of conditions and the following disclaimer 14// in the documentation and/or other materials provided with the 15// distribution. 16// * Neither the name of Google Inc. nor the names of its 17// contributors may be used to endorse or promote products derived from 18// this software without specific prior written permission. 19// 20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 32package proto_test 33 34import ( 35 "bytes" 36 "errors" 37 "io/ioutil" 38 "math" 39 "strings" 40 "testing" 41 42 "github.com/golang/protobuf/proto" 43 44 proto3pb "github.com/golang/protobuf/proto/proto3_proto" 45 pb "github.com/golang/protobuf/proto/testdata" 46) 47 48// textMessage implements the methods that allow it to marshal and unmarshal 49// itself as text. 50type textMessage struct { 51} 52 53func (*textMessage) MarshalText() ([]byte, error) { 54 return []byte("custom"), nil 55} 56 57func (*textMessage) UnmarshalText(bytes []byte) error { 58 if string(bytes) != "custom" { 59 return errors.New("expected 'custom'") 60 } 61 return nil 62} 63 64func (*textMessage) Reset() {} 65func (*textMessage) String() string { return "" } 66func (*textMessage) ProtoMessage() {} 67 68func newTestMessage() *pb.MyMessage { 69 msg := &pb.MyMessage{ 70 Count: proto.Int32(42), 71 Name: proto.String("Dave"), 72 Quote: proto.String(`"I didn't want to go."`), 73 Pet: []string{"bunny", "kitty", "horsey"}, 74 Inner: &pb.InnerMessage{ 75 Host: proto.String("footrest.syd"), 76 Port: proto.Int32(7001), 77 Connected: proto.Bool(true), 78 }, 79 Others: []*pb.OtherMessage{ 80 { 81 Key: proto.Int64(0xdeadbeef), 82 Value: []byte{1, 65, 7, 12}, 83 }, 84 { 85 Weight: proto.Float32(6.022), 86 Inner: &pb.InnerMessage{ 87 Host: proto.String("lesha.mtv"), 88 Port: proto.Int32(8002), 89 }, 90 }, 91 }, 92 Bikeshed: pb.MyMessage_BLUE.Enum(), 93 Somegroup: &pb.MyMessage_SomeGroup{ 94 GroupField: proto.Int32(8), 95 }, 96 // One normally wouldn't do this. 97 // This is an undeclared tag 13, as a varint (wire type 0) with value 4. 98 XXX_unrecognized: []byte{13<<3 | 0, 4}, 99 } 100 ext := &pb.Ext{ 101 Data: proto.String("Big gobs for big rats"), 102 } 103 if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil { 104 panic(err) 105 } 106 greetings := []string{"adg", "easy", "cow"} 107 if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil { 108 panic(err) 109 } 110 111 // Add an unknown extension. We marshal a pb.Ext, and fake the ID. 112 b, err := proto.Marshal(&pb.Ext{Data: proto.String("3G skiing")}) 113 if err != nil { 114 panic(err) 115 } 116 b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...) 117 proto.SetRawExtension(msg, 201, b) 118 119 // Extensions can be plain fields, too, so let's test that. 120 b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19) 121 proto.SetRawExtension(msg, 202, b) 122 123 return msg 124} 125 126const text = `count: 42 127name: "Dave" 128quote: "\"I didn't want to go.\"" 129pet: "bunny" 130pet: "kitty" 131pet: "horsey" 132inner: < 133 host: "footrest.syd" 134 port: 7001 135 connected: true 136> 137others: < 138 key: 3735928559 139 value: "\001A\007\014" 140> 141others: < 142 weight: 6.022 143 inner: < 144 host: "lesha.mtv" 145 port: 8002 146 > 147> 148bikeshed: BLUE 149SomeGroup { 150 group_field: 8 151} 152/* 2 unknown bytes */ 15313: 4 154[testdata.Ext.more]: < 155 data: "Big gobs for big rats" 156> 157[testdata.greeting]: "adg" 158[testdata.greeting]: "easy" 159[testdata.greeting]: "cow" 160/* 13 unknown bytes */ 161201: "\t3G skiing" 162/* 3 unknown bytes */ 163202: 19 164` 165 166func TestMarshalText(t *testing.T) { 167 buf := new(bytes.Buffer) 168 if err := proto.MarshalText(buf, newTestMessage()); err != nil { 169 t.Fatalf("proto.MarshalText: %v", err) 170 } 171 s := buf.String() 172 if s != text { 173 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, text) 174 } 175} 176 177func TestMarshalTextCustomMessage(t *testing.T) { 178 buf := new(bytes.Buffer) 179 if err := proto.MarshalText(buf, &textMessage{}); err != nil { 180 t.Fatalf("proto.MarshalText: %v", err) 181 } 182 s := buf.String() 183 if s != "custom" { 184 t.Errorf("Got %q, expected %q", s, "custom") 185 } 186} 187func TestMarshalTextNil(t *testing.T) { 188 want := "<nil>" 189 tests := []proto.Message{nil, (*pb.MyMessage)(nil)} 190 for i, test := range tests { 191 buf := new(bytes.Buffer) 192 if err := proto.MarshalText(buf, test); err != nil { 193 t.Fatal(err) 194 } 195 if got := buf.String(); got != want { 196 t.Errorf("%d: got %q want %q", i, got, want) 197 } 198 } 199} 200 201func TestMarshalTextUnknownEnum(t *testing.T) { 202 // The Color enum only specifies values 0-2. 203 m := &pb.MyMessage{Bikeshed: pb.MyMessage_Color(3).Enum()} 204 got := m.String() 205 const want = `bikeshed:3 ` 206 if got != want { 207 t.Errorf("\n got %q\nwant %q", got, want) 208 } 209} 210 211func TestTextOneof(t *testing.T) { 212 tests := []struct { 213 m proto.Message 214 want string 215 }{ 216 // zero message 217 {&pb.Communique{}, ``}, 218 // scalar field 219 {&pb.Communique{Union: &pb.Communique_Number{4}}, `number:4`}, 220 // message field 221 {&pb.Communique{Union: &pb.Communique_Msg{ 222 &pb.Strings{StringField: proto.String("why hello!")}, 223 }}, `msg:<string_field:"why hello!" >`}, 224 // bad oneof (should not panic) 225 {&pb.Communique{Union: &pb.Communique_Msg{nil}}, `msg:/* nil */`}, 226 } 227 for _, test := range tests { 228 got := strings.TrimSpace(test.m.String()) 229 if got != test.want { 230 t.Errorf("\n got %s\nwant %s", got, test.want) 231 } 232 } 233} 234 235func BenchmarkMarshalTextBuffered(b *testing.B) { 236 buf := new(bytes.Buffer) 237 m := newTestMessage() 238 for i := 0; i < b.N; i++ { 239 buf.Reset() 240 proto.MarshalText(buf, m) 241 } 242} 243 244func BenchmarkMarshalTextUnbuffered(b *testing.B) { 245 w := ioutil.Discard 246 m := newTestMessage() 247 for i := 0; i < b.N; i++ { 248 proto.MarshalText(w, m) 249 } 250} 251 252func compact(src string) string { 253 // s/[ \n]+/ /g; s/ $//; 254 dst := make([]byte, len(src)) 255 space, comment := false, false 256 j := 0 257 for i := 0; i < len(src); i++ { 258 if strings.HasPrefix(src[i:], "/*") { 259 comment = true 260 i++ 261 continue 262 } 263 if comment && strings.HasPrefix(src[i:], "*/") { 264 comment = false 265 i++ 266 continue 267 } 268 if comment { 269 continue 270 } 271 c := src[i] 272 if c == ' ' || c == '\n' { 273 space = true 274 continue 275 } 276 if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') { 277 space = false 278 } 279 if c == '{' { 280 space = false 281 } 282 if space { 283 dst[j] = ' ' 284 j++ 285 space = false 286 } 287 dst[j] = c 288 j++ 289 } 290 if space { 291 dst[j] = ' ' 292 j++ 293 } 294 return string(dst[0:j]) 295} 296 297var compactText = compact(text) 298 299func TestCompactText(t *testing.T) { 300 s := proto.CompactTextString(newTestMessage()) 301 if s != compactText { 302 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v\n===\n", s, compactText) 303 } 304} 305 306func TestStringEscaping(t *testing.T) { 307 testCases := []struct { 308 in *pb.Strings 309 out string 310 }{ 311 { 312 // Test data from C++ test (TextFormatTest.StringEscape). 313 // Single divergence: we don't escape apostrophes. 314 &pb.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces")}, 315 "string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"\n", 316 }, 317 { 318 // Test data from the same C++ test. 319 &pb.Strings{StringField: proto.String("\350\260\267\346\255\214")}, 320 "string_field: \"\\350\\260\\267\\346\\255\\214\"\n", 321 }, 322 { 323 // Some UTF-8. 324 &pb.Strings{StringField: proto.String("\x00\x01\xff\x81")}, 325 `string_field: "\000\001\377\201"` + "\n", 326 }, 327 } 328 329 for i, tc := range testCases { 330 var buf bytes.Buffer 331 if err := proto.MarshalText(&buf, tc.in); err != nil { 332 t.Errorf("proto.MarsalText: %v", err) 333 continue 334 } 335 s := buf.String() 336 if s != tc.out { 337 t.Errorf("#%d: Got:\n%s\nExpected:\n%s\n", i, s, tc.out) 338 continue 339 } 340 341 // Check round-trip. 342 pb := new(pb.Strings) 343 if err := proto.UnmarshalText(s, pb); err != nil { 344 t.Errorf("#%d: UnmarshalText: %v", i, err) 345 continue 346 } 347 if !proto.Equal(pb, tc.in) { 348 t.Errorf("#%d: Round-trip failed:\nstart: %v\n end: %v", i, tc.in, pb) 349 } 350 } 351} 352 353// A limitedWriter accepts some output before it fails. 354// This is a proxy for something like a nearly-full or imminently-failing disk, 355// or a network connection that is about to die. 356type limitedWriter struct { 357 b bytes.Buffer 358 limit int 359} 360 361var outOfSpace = errors.New("proto: insufficient space") 362 363func (w *limitedWriter) Write(p []byte) (n int, err error) { 364 var avail = w.limit - w.b.Len() 365 if avail <= 0 { 366 return 0, outOfSpace 367 } 368 if len(p) <= avail { 369 return w.b.Write(p) 370 } 371 n, _ = w.b.Write(p[:avail]) 372 return n, outOfSpace 373} 374 375func TestMarshalTextFailing(t *testing.T) { 376 // Try lots of different sizes to exercise more error code-paths. 377 for lim := 0; lim < len(text); lim++ { 378 buf := new(limitedWriter) 379 buf.limit = lim 380 err := proto.MarshalText(buf, newTestMessage()) 381 // We expect a certain error, but also some partial results in the buffer. 382 if err != outOfSpace { 383 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", err, outOfSpace) 384 } 385 s := buf.b.String() 386 x := text[:buf.limit] 387 if s != x { 388 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, x) 389 } 390 } 391} 392 393func TestFloats(t *testing.T) { 394 tests := []struct { 395 f float64 396 want string 397 }{ 398 {0, "0"}, 399 {4.7, "4.7"}, 400 {math.Inf(1), "inf"}, 401 {math.Inf(-1), "-inf"}, 402 {math.NaN(), "nan"}, 403 } 404 for _, test := range tests { 405 msg := &pb.FloatingPoint{F: &test.f} 406 got := strings.TrimSpace(msg.String()) 407 want := `f:` + test.want 408 if got != want { 409 t.Errorf("f=%f: got %q, want %q", test.f, got, want) 410 } 411 } 412} 413 414func TestRepeatedNilText(t *testing.T) { 415 m := &pb.MessageList{ 416 Message: []*pb.MessageList_Message{ 417 nil, 418 &pb.MessageList_Message{ 419 Name: proto.String("Horse"), 420 }, 421 nil, 422 }, 423 } 424 want := `Message <nil> 425Message { 426 name: "Horse" 427} 428Message <nil> 429` 430 if s := proto.MarshalTextString(m); s != want { 431 t.Errorf(" got: %s\nwant: %s", s, want) 432 } 433} 434 435func TestProto3Text(t *testing.T) { 436 tests := []struct { 437 m proto.Message 438 want string 439 }{ 440 // zero message 441 {&proto3pb.Message{}, ``}, 442 // zero message except for an empty byte slice 443 {&proto3pb.Message{Data: []byte{}}, ``}, 444 // trivial case 445 {&proto3pb.Message{Name: "Rob", HeightInCm: 175}, `name:"Rob" height_in_cm:175`}, 446 // empty map 447 {&pb.MessageWithMap{}, ``}, 448 // non-empty map; map format is the same as a repeated struct, 449 // and they are sorted by key (numerically for numeric keys). 450 { 451 &pb.MessageWithMap{NameMapping: map[int32]string{ 452 -1: "Negatory", 453 7: "Lucky", 454 1234: "Feist", 455 6345789: "Otis", 456 }}, 457 `name_mapping:<key:-1 value:"Negatory" > ` + 458 `name_mapping:<key:7 value:"Lucky" > ` + 459 `name_mapping:<key:1234 value:"Feist" > ` + 460 `name_mapping:<key:6345789 value:"Otis" >`, 461 }, 462 // map with nil value; not well-defined, but we shouldn't crash 463 { 464 &pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{7: nil}}, 465 `msg_mapping:<key:7 >`, 466 }, 467 } 468 for _, test := range tests { 469 got := strings.TrimSpace(test.m.String()) 470 if got != test.want { 471 t.Errorf("\n got %s\nwant %s", got, test.want) 472 } 473 } 474} 475