blob: 3eabacac8f9977e7c8cb97e54adb0c740a1a96f7 [file] [log] [blame]
Rob Pikeaaa3a622010-03-20 22:32:34 -07001// Go support for Protocol Buffers - Google's data interchange format
2//
David Symondsee6e9c52012-11-29 08:51:07 +11003// Copyright 2010 The Go Authors. All rights reserved.
David Symonds558f13f2014-11-24 10:28:53 +11004// https://github.com/golang/protobuf
Rob Pikeaaa3a622010-03-20 22:32:34 -07005//
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"
David Symonds4dc589e2012-12-06 14:05:48 +110036 "errors"
37 "io/ioutil"
David Symonds9cc87e32013-06-08 12:27:15 +100038 "math"
David Symonds1d72f7a2011-08-19 18:28:52 +100039 "strings"
Rob Pikeaaa3a622010-03-20 22:32:34 -070040 "testing"
David Symondsaa922ff2011-07-19 14:58:06 +100041
David Symonds558f13f2014-11-24 10:28:53 +110042 "github.com/golang/protobuf/proto"
David Symondsaa922ff2011-07-19 14:58:06 +100043
David Symondsa8de2842015-03-20 16:24:29 +110044 proto3pb "github.com/golang/protobuf/proto/proto3_proto"
45 pb "github.com/golang/protobuf/proto/testdata"
Rob Pikeaaa3a622010-03-20 22:32:34 -070046)
47
David Symonds267e8052014-02-19 14:50:51 +110048// 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
David Symondsaa922ff2011-07-19 14:58:06 +100068func 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."`),
Rob Pike9caa5b92010-05-11 16:04:57 -070073 Pet: []string{"bunny", "kitty", "horsey"},
David Symondsaa922ff2011-07-19 14:58:06 +100074 Inner: &pb.InnerMessage{
75 Host: proto.String("footrest.syd"),
76 Port: proto.Int32(7001),
77 Connected: proto.Bool(true),
Rob Pikeaaa3a622010-03-20 22:32:34 -070078 },
David Symondsaa922ff2011-07-19 14:58:06 +100079 Others: []*pb.OtherMessage{
David Symonds92dd6c12012-03-23 10:59:49 +110080 {
David Symondsaa922ff2011-07-19 14:58:06 +100081 Key: proto.Int64(0xdeadbeef),
Rob Pikeaaa3a622010-03-20 22:32:34 -070082 Value: []byte{1, 65, 7, 12},
83 },
David Symonds92dd6c12012-03-23 10:59:49 +110084 {
David Symondsaa922ff2011-07-19 14:58:06 +100085 Weight: proto.Float32(6.022),
86 Inner: &pb.InnerMessage{
87 Host: proto.String("lesha.mtv"),
88 Port: proto.Int32(8002),
Rob Pikeaaa3a622010-03-20 22:32:34 -070089 },
90 },
91 },
David Symondsefeca9a2012-05-08 10:36:04 +100092 Bikeshed: pb.MyMessage_BLUE.Enum(),
David Symondsaa922ff2011-07-19 14:58:06 +100093 Somegroup: &pb.MyMessage_SomeGroup{
94 GroupField: proto.Int32(8),
David Symonds9f402812011-04-28 18:08:44 +100095 },
David Symonds1d72f7a2011-08-19 18:28:52 +100096 // 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},
Rob Pikeaaa3a622010-03-20 22:32:34 -070099 }
David Symondsaa922ff2011-07-19 14:58:06 +1000100 ext := &pb.Ext{
101 Data: proto.String("Big gobs for big rats"),
David Symondse37856c2011-06-22 12:52:53 +1000102 }
David Symondsaa922ff2011-07-19 14:58:06 +1000103 if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil {
David Symondse37856c2011-06-22 12:52:53 +1000104 panic(err)
105 }
David Symonds61826da2012-05-05 09:31:28 +1000106 greetings := []string{"adg", "easy", "cow"}
107 if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil {
108 panic(err)
109 }
David Symonds1d72f7a2011-08-19 18:28:52 +1000110
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 }
David Symonds54531052011-12-08 12:00:31 +1100116 b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...)
117 proto.SetRawExtension(msg, 201, b)
David Symonds1d72f7a2011-08-19 18:28:52 +1000118
119 // Extensions can be plain fields, too, so let's test that.
David Symonds54531052011-12-08 12:00:31 +1100120 b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19)
121 proto.SetRawExtension(msg, 202, b)
David Symonds1d72f7a2011-08-19 18:28:52 +1000122
David Symondse37856c2011-06-22 12:52:53 +1000123 return msg
Rob Pikeaaa3a622010-03-20 22:32:34 -0700124}
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
David Symonds4c95bfe2011-09-13 14:43:27 +1000139 value: "\001A\007\014"
Rob Pikeaaa3a622010-03-20 22:32:34 -0700140>
141others: <
142 weight: 6.022
143 inner: <
144 host: "lesha.mtv"
145 port: 8002
146 >
147>
148bikeshed: BLUE
David Symonds9f402812011-04-28 18:08:44 +1000149SomeGroup {
150 group_field: 8
151}
David Symonds1d72f7a2011-08-19 18:28:52 +1000152/* 2 unknown bytes */
David Symonds20370902013-03-23 17:20:01 +110015313: 4
Rob Pikeb7907bf2012-02-13 14:25:20 +1100154[testdata.Ext.more]: <
David Symondse37856c2011-06-22 12:52:53 +1000155 data: "Big gobs for big rats"
156>
David Symonds61826da2012-05-05 09:31:28 +1000157[testdata.greeting]: "adg"
158[testdata.greeting]: "easy"
159[testdata.greeting]: "cow"
David Symonds1d72f7a2011-08-19 18:28:52 +1000160/* 13 unknown bytes */
David Symonds20370902013-03-23 17:20:01 +1100161201: "\t3G skiing"
David Symonds1d72f7a2011-08-19 18:28:52 +1000162/* 3 unknown bytes */
David Symonds20370902013-03-23 17:20:01 +1100163202: 19
Rob Pikeaaa3a622010-03-20 22:32:34 -0700164`
165
David Symonds4dc589e2012-12-06 14:05:48 +1100166func TestMarshalText(t *testing.T) {
Rob Pikeaaa3a622010-03-20 22:32:34 -0700167 buf := new(bytes.Buffer)
David Symonds4dc589e2012-12-06 14:05:48 +1100168 if err := proto.MarshalText(buf, newTestMessage()); err != nil {
169 t.Fatalf("proto.MarshalText: %v", err)
170 }
Rob Pikeaaa3a622010-03-20 22:32:34 -0700171 s := buf.String()
172 if s != text {
173 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, text)
174 }
175}
176
David Symonds267e8052014-02-19 14:50:51 +1100177func 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}
David Symonds6e8ab872013-01-30 17:07:26 +1100187func 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
David Symondsf8a1fcc2013-05-03 08:51:23 +1000201func 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
David Symonds59b73b32015-08-24 13:22:02 +1000211func 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!" >`},
David Symondsa57d2912015-08-27 13:00:00 +1000224 // bad oneof (should not panic)
225 {&pb.Communique{Union: &pb.Communique_Msg{nil}}, `msg:/* nil */`},
David Symonds59b73b32015-08-24 13:22:02 +1000226 }
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
David Symonds4dc589e2012-12-06 14:05:48 +1100235func BenchmarkMarshalTextBuffered(b *testing.B) {
David Symondsdbdc4212012-11-08 08:20:35 +1100236 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
David Symonds4dc589e2012-12-06 14:05:48 +1100244func 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
Rob Pikeaaa3a622010-03-20 22:32:34 -0700252func compact(src string) string {
David Symondse37856c2011-06-22 12:52:53 +1000253 // s/[ \n]+/ /g; s/ $//;
Rob Pikeaaa3a622010-03-20 22:32:34 -0700254 dst := make([]byte, len(src))
David Symonds1d72f7a2011-08-19 18:28:52 +1000255 space, comment := false, false
Rob Pikeaaa3a622010-03-20 22:32:34 -0700256 j := 0
257 for i := 0; i < len(src); i++ {
David Symonds1d72f7a2011-08-19 18:28:52 +1000258 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 }
Rob Pikeaaa3a622010-03-20 22:32:34 -0700271 c := src[i]
272 if c == ' ' || c == '\n' {
273 space = true
274 continue
275 }
David Symonds9f402812011-04-28 18:08:44 +1000276 if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') {
277 space = false
278 }
279 if c == '{' {
Rob Pikeaaa3a622010-03-20 22:32:34 -0700280 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) {
David Symondsaa922ff2011-07-19 14:58:06 +1000300 s := proto.CompactTextString(newTestMessage())
Rob Pikeaaa3a622010-03-20 22:32:34 -0700301 if s != compactText {
David Symonds4c95bfe2011-09-13 14:43:27 +1000302 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 },
David Symondsfa94a1e2012-09-24 13:21:49 +1000322 {
323 // Some UTF-8.
324 &pb.Strings{StringField: proto.String("\x00\x01\xff\x81")},
325 `string_field: "\000\001\377\201"` + "\n",
326 },
David Symonds4c95bfe2011-09-13 14:43:27 +1000327 }
328
329 for i, tc := range testCases {
330 var buf bytes.Buffer
David Symonds4dc589e2012-12-06 14:05:48 +1100331 if err := proto.MarshalText(&buf, tc.in); err != nil {
332 t.Errorf("proto.MarsalText: %v", err)
333 continue
334 }
David Symondsfa94a1e2012-09-24 13:21:49 +1000335 s := buf.String()
336 if s != tc.out {
David Symonds4c95bfe2011-09-13 14:43:27 +1000337 t.Errorf("#%d: Got:\n%s\nExpected:\n%s\n", i, s, tc.out)
David Symondsfa94a1e2012-09-24 13:21:49 +1000338 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)
David Symonds4c95bfe2011-09-13 14:43:27 +1000349 }
Rob Pikeaaa3a622010-03-20 22:32:34 -0700350 }
351}
David Symonds4dc589e2012-12-06 14:05:48 +1100352
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
David Symondsa7f3a0f2013-09-09 13:32:33 +1000361var outOfSpace = errors.New("proto: insufficient space")
David Symonds4dc589e2012-12-06 14:05:48 +1100362
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}
David Symonds9cc87e32013-06-08 12:27:15 +1000392
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}
David Symonds4b9d2e12014-04-15 18:22:22 +1000413
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}
David Symondsabd3b412014-11-28 11:43:44 +1100434
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`},
David Symonds3ea3e052014-12-22 16:15:28 +1100446 // empty map
447 {&pb.MessageWithMap{}, ``},
David Symonds66836542015-07-25 20:00:00 +1000448 // non-empty map; map format is the same as a repeated struct,
449 // and they are sorted by key (numerically for numeric keys).
David Symonds3ea3e052014-12-22 16:15:28 +1100450 {
David Symonds66836542015-07-25 20:00:00 +1000451 &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" >`,
David Symonds3ea3e052014-12-22 16:15:28 +1100461 },
David Symonds34a5f242015-05-26 11:05:00 +1000462 // 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 },
David Symondsabd3b412014-11-28 11:43:44 +1100467 }
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}