blob: 224a484e50249de3affaf51007d2b6820820f22f [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.
Rob Pikeaaa3a622010-03-20 22:32:34 -07004// http://code.google.com/p/goprotobuf/
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"
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
Rob Pike3f6f2d82011-12-18 13:55:35 -080042 "code.google.com/p/goprotobuf/proto"
David Symondsaa922ff2011-07-19 14:58:06 +100043
David Symonds704096f2012-03-15 15:10:26 +110044 pb "./testdata"
Rob Pikeaaa3a622010-03-20 22:32:34 -070045)
46
David Symonds267e8052014-02-19 14:50:51 +110047// textMessage implements the methods that allow it to marshal and unmarshal
48// itself as text.
49type textMessage struct {
50}
51
52func (*textMessage) MarshalText() ([]byte, error) {
53 return []byte("custom"), nil
54}
55
56func (*textMessage) UnmarshalText(bytes []byte) error {
57 if string(bytes) != "custom" {
58 return errors.New("expected 'custom'")
59 }
60 return nil
61}
62
63func (*textMessage) Reset() {}
64func (*textMessage) String() string { return "" }
65func (*textMessage) ProtoMessage() {}
66
David Symondsaa922ff2011-07-19 14:58:06 +100067func newTestMessage() *pb.MyMessage {
68 msg := &pb.MyMessage{
69 Count: proto.Int32(42),
70 Name: proto.String("Dave"),
71 Quote: proto.String(`"I didn't want to go."`),
Rob Pike9caa5b92010-05-11 16:04:57 -070072 Pet: []string{"bunny", "kitty", "horsey"},
David Symondsaa922ff2011-07-19 14:58:06 +100073 Inner: &pb.InnerMessage{
74 Host: proto.String("footrest.syd"),
75 Port: proto.Int32(7001),
76 Connected: proto.Bool(true),
Rob Pikeaaa3a622010-03-20 22:32:34 -070077 },
David Symondsaa922ff2011-07-19 14:58:06 +100078 Others: []*pb.OtherMessage{
David Symonds92dd6c12012-03-23 10:59:49 +110079 {
David Symondsaa922ff2011-07-19 14:58:06 +100080 Key: proto.Int64(0xdeadbeef),
Rob Pikeaaa3a622010-03-20 22:32:34 -070081 Value: []byte{1, 65, 7, 12},
82 },
David Symonds92dd6c12012-03-23 10:59:49 +110083 {
David Symondsaa922ff2011-07-19 14:58:06 +100084 Weight: proto.Float32(6.022),
85 Inner: &pb.InnerMessage{
86 Host: proto.String("lesha.mtv"),
87 Port: proto.Int32(8002),
Rob Pikeaaa3a622010-03-20 22:32:34 -070088 },
89 },
90 },
David Symondsefeca9a2012-05-08 10:36:04 +100091 Bikeshed: pb.MyMessage_BLUE.Enum(),
David Symondsaa922ff2011-07-19 14:58:06 +100092 Somegroup: &pb.MyMessage_SomeGroup{
93 GroupField: proto.Int32(8),
David Symonds9f402812011-04-28 18:08:44 +100094 },
David Symonds1d72f7a2011-08-19 18:28:52 +100095 // One normally wouldn't do this.
96 // This is an undeclared tag 13, as a varint (wire type 0) with value 4.
97 XXX_unrecognized: []byte{13<<3 | 0, 4},
Rob Pikeaaa3a622010-03-20 22:32:34 -070098 }
David Symondsaa922ff2011-07-19 14:58:06 +100099 ext := &pb.Ext{
100 Data: proto.String("Big gobs for big rats"),
David Symondse37856c2011-06-22 12:52:53 +1000101 }
David Symondsaa922ff2011-07-19 14:58:06 +1000102 if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil {
David Symondse37856c2011-06-22 12:52:53 +1000103 panic(err)
104 }
David Symonds61826da2012-05-05 09:31:28 +1000105 greetings := []string{"adg", "easy", "cow"}
106 if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil {
107 panic(err)
108 }
David Symonds1d72f7a2011-08-19 18:28:52 +1000109
110 // Add an unknown extension. We marshal a pb.Ext, and fake the ID.
111 b, err := proto.Marshal(&pb.Ext{Data: proto.String("3G skiing")})
112 if err != nil {
113 panic(err)
114 }
David Symonds54531052011-12-08 12:00:31 +1100115 b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...)
116 proto.SetRawExtension(msg, 201, b)
David Symonds1d72f7a2011-08-19 18:28:52 +1000117
118 // Extensions can be plain fields, too, so let's test that.
David Symonds54531052011-12-08 12:00:31 +1100119 b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19)
120 proto.SetRawExtension(msg, 202, b)
David Symonds1d72f7a2011-08-19 18:28:52 +1000121
David Symondse37856c2011-06-22 12:52:53 +1000122 return msg
Rob Pikeaaa3a622010-03-20 22:32:34 -0700123}
124
125const text = `count: 42
126name: "Dave"
127quote: "\"I didn't want to go.\""
128pet: "bunny"
129pet: "kitty"
130pet: "horsey"
131inner: <
132 host: "footrest.syd"
133 port: 7001
134 connected: true
135>
136others: <
137 key: 3735928559
David Symonds4c95bfe2011-09-13 14:43:27 +1000138 value: "\001A\007\014"
Rob Pikeaaa3a622010-03-20 22:32:34 -0700139>
140others: <
141 weight: 6.022
142 inner: <
143 host: "lesha.mtv"
144 port: 8002
145 >
146>
147bikeshed: BLUE
David Symonds9f402812011-04-28 18:08:44 +1000148SomeGroup {
149 group_field: 8
150}
David Symonds1d72f7a2011-08-19 18:28:52 +1000151/* 2 unknown bytes */
David Symonds20370902013-03-23 17:20:01 +110015213: 4
Rob Pikeb7907bf2012-02-13 14:25:20 +1100153[testdata.Ext.more]: <
David Symondse37856c2011-06-22 12:52:53 +1000154 data: "Big gobs for big rats"
155>
David Symonds61826da2012-05-05 09:31:28 +1000156[testdata.greeting]: "adg"
157[testdata.greeting]: "easy"
158[testdata.greeting]: "cow"
David Symonds1d72f7a2011-08-19 18:28:52 +1000159/* 13 unknown bytes */
David Symonds20370902013-03-23 17:20:01 +1100160201: "\t3G skiing"
David Symonds1d72f7a2011-08-19 18:28:52 +1000161/* 3 unknown bytes */
David Symonds20370902013-03-23 17:20:01 +1100162202: 19
Rob Pikeaaa3a622010-03-20 22:32:34 -0700163`
164
David Symonds4dc589e2012-12-06 14:05:48 +1100165func TestMarshalText(t *testing.T) {
Rob Pikeaaa3a622010-03-20 22:32:34 -0700166 buf := new(bytes.Buffer)
David Symonds4dc589e2012-12-06 14:05:48 +1100167 if err := proto.MarshalText(buf, newTestMessage()); err != nil {
168 t.Fatalf("proto.MarshalText: %v", err)
169 }
Rob Pikeaaa3a622010-03-20 22:32:34 -0700170 s := buf.String()
171 if s != text {
172 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, text)
173 }
174}
175
David Symonds267e8052014-02-19 14:50:51 +1100176func TestMarshalTextCustomMessage(t *testing.T) {
177 buf := new(bytes.Buffer)
178 if err := proto.MarshalText(buf, &textMessage{}); err != nil {
179 t.Fatalf("proto.MarshalText: %v", err)
180 }
181 s := buf.String()
182 if s != "custom" {
183 t.Errorf("Got %q, expected %q", s, "custom")
184 }
185}
David Symonds6e8ab872013-01-30 17:07:26 +1100186func TestMarshalTextNil(t *testing.T) {
187 want := "<nil>"
188 tests := []proto.Message{nil, (*pb.MyMessage)(nil)}
189 for i, test := range tests {
190 buf := new(bytes.Buffer)
191 if err := proto.MarshalText(buf, test); err != nil {
192 t.Fatal(err)
193 }
194 if got := buf.String(); got != want {
195 t.Errorf("%d: got %q want %q", i, got, want)
196 }
197 }
198}
199
David Symondsf8a1fcc2013-05-03 08:51:23 +1000200func TestMarshalTextUnknownEnum(t *testing.T) {
201 // The Color enum only specifies values 0-2.
202 m := &pb.MyMessage{Bikeshed: pb.MyMessage_Color(3).Enum()}
203 got := m.String()
204 const want = `bikeshed:3 `
205 if got != want {
206 t.Errorf("\n got %q\nwant %q", got, want)
207 }
208}
209
David Symonds4dc589e2012-12-06 14:05:48 +1100210func BenchmarkMarshalTextBuffered(b *testing.B) {
David Symondsdbdc4212012-11-08 08:20:35 +1100211 buf := new(bytes.Buffer)
212 m := newTestMessage()
213 for i := 0; i < b.N; i++ {
214 buf.Reset()
215 proto.MarshalText(buf, m)
216 }
217}
218
David Symonds4dc589e2012-12-06 14:05:48 +1100219func BenchmarkMarshalTextUnbuffered(b *testing.B) {
220 w := ioutil.Discard
221 m := newTestMessage()
222 for i := 0; i < b.N; i++ {
223 proto.MarshalText(w, m)
224 }
225}
226
Rob Pikeaaa3a622010-03-20 22:32:34 -0700227func compact(src string) string {
David Symondse37856c2011-06-22 12:52:53 +1000228 // s/[ \n]+/ /g; s/ $//;
Rob Pikeaaa3a622010-03-20 22:32:34 -0700229 dst := make([]byte, len(src))
David Symonds1d72f7a2011-08-19 18:28:52 +1000230 space, comment := false, false
Rob Pikeaaa3a622010-03-20 22:32:34 -0700231 j := 0
232 for i := 0; i < len(src); i++ {
David Symonds1d72f7a2011-08-19 18:28:52 +1000233 if strings.HasPrefix(src[i:], "/*") {
234 comment = true
235 i++
236 continue
237 }
238 if comment && strings.HasPrefix(src[i:], "*/") {
239 comment = false
240 i++
241 continue
242 }
243 if comment {
244 continue
245 }
Rob Pikeaaa3a622010-03-20 22:32:34 -0700246 c := src[i]
247 if c == ' ' || c == '\n' {
248 space = true
249 continue
250 }
David Symonds9f402812011-04-28 18:08:44 +1000251 if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') {
252 space = false
253 }
254 if c == '{' {
Rob Pikeaaa3a622010-03-20 22:32:34 -0700255 space = false
256 }
257 if space {
258 dst[j] = ' '
259 j++
260 space = false
261 }
262 dst[j] = c
263 j++
264 }
265 if space {
266 dst[j] = ' '
267 j++
268 }
269 return string(dst[0:j])
270}
271
272var compactText = compact(text)
273
274func TestCompactText(t *testing.T) {
David Symondsaa922ff2011-07-19 14:58:06 +1000275 s := proto.CompactTextString(newTestMessage())
Rob Pikeaaa3a622010-03-20 22:32:34 -0700276 if s != compactText {
David Symonds4c95bfe2011-09-13 14:43:27 +1000277 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v\n===\n", s, compactText)
278 }
279}
280
281func TestStringEscaping(t *testing.T) {
282 testCases := []struct {
283 in *pb.Strings
284 out string
285 }{
286 {
287 // Test data from C++ test (TextFormatTest.StringEscape).
288 // Single divergence: we don't escape apostrophes.
289 &pb.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces")},
290 "string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"\n",
291 },
292 {
293 // Test data from the same C++ test.
294 &pb.Strings{StringField: proto.String("\350\260\267\346\255\214")},
295 "string_field: \"\\350\\260\\267\\346\\255\\214\"\n",
296 },
David Symondsfa94a1e2012-09-24 13:21:49 +1000297 {
298 // Some UTF-8.
299 &pb.Strings{StringField: proto.String("\x00\x01\xff\x81")},
300 `string_field: "\000\001\377\201"` + "\n",
301 },
David Symonds4c95bfe2011-09-13 14:43:27 +1000302 }
303
304 for i, tc := range testCases {
305 var buf bytes.Buffer
David Symonds4dc589e2012-12-06 14:05:48 +1100306 if err := proto.MarshalText(&buf, tc.in); err != nil {
307 t.Errorf("proto.MarsalText: %v", err)
308 continue
309 }
David Symondsfa94a1e2012-09-24 13:21:49 +1000310 s := buf.String()
311 if s != tc.out {
David Symonds4c95bfe2011-09-13 14:43:27 +1000312 t.Errorf("#%d: Got:\n%s\nExpected:\n%s\n", i, s, tc.out)
David Symondsfa94a1e2012-09-24 13:21:49 +1000313 continue
314 }
315
316 // Check round-trip.
317 pb := new(pb.Strings)
318 if err := proto.UnmarshalText(s, pb); err != nil {
319 t.Errorf("#%d: UnmarshalText: %v", i, err)
320 continue
321 }
322 if !proto.Equal(pb, tc.in) {
323 t.Errorf("#%d: Round-trip failed:\nstart: %v\n end: %v", i, tc.in, pb)
David Symonds4c95bfe2011-09-13 14:43:27 +1000324 }
Rob Pikeaaa3a622010-03-20 22:32:34 -0700325 }
326}
David Symonds4dc589e2012-12-06 14:05:48 +1100327
328// A limitedWriter accepts some output before it fails.
329// This is a proxy for something like a nearly-full or imminently-failing disk,
330// or a network connection that is about to die.
331type limitedWriter struct {
332 b bytes.Buffer
333 limit int
334}
335
David Symondsa7f3a0f2013-09-09 13:32:33 +1000336var outOfSpace = errors.New("proto: insufficient space")
David Symonds4dc589e2012-12-06 14:05:48 +1100337
338func (w *limitedWriter) Write(p []byte) (n int, err error) {
339 var avail = w.limit - w.b.Len()
340 if avail <= 0 {
341 return 0, outOfSpace
342 }
343 if len(p) <= avail {
344 return w.b.Write(p)
345 }
346 n, _ = w.b.Write(p[:avail])
347 return n, outOfSpace
348}
349
350func TestMarshalTextFailing(t *testing.T) {
351 // Try lots of different sizes to exercise more error code-paths.
352 for lim := 0; lim < len(text); lim++ {
353 buf := new(limitedWriter)
354 buf.limit = lim
355 err := proto.MarshalText(buf, newTestMessage())
356 // We expect a certain error, but also some partial results in the buffer.
357 if err != outOfSpace {
358 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", err, outOfSpace)
359 }
360 s := buf.b.String()
361 x := text[:buf.limit]
362 if s != x {
363 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, x)
364 }
365 }
366}
David Symonds9cc87e32013-06-08 12:27:15 +1000367
368func TestFloats(t *testing.T) {
369 tests := []struct {
370 f float64
371 want string
372 }{
373 {0, "0"},
374 {4.7, "4.7"},
375 {math.Inf(1), "inf"},
376 {math.Inf(-1), "-inf"},
377 {math.NaN(), "nan"},
378 }
379 for _, test := range tests {
380 msg := &pb.FloatingPoint{F: &test.f}
381 got := strings.TrimSpace(msg.String())
382 want := `f:` + test.want
383 if got != want {
384 t.Errorf("f=%f: got %q, want %q", test.f, got, want)
385 }
386 }
387}
David Symonds4b9d2e12014-04-15 18:22:22 +1000388
389func TestRepeatedNilText(t *testing.T) {
390 m := &pb.MessageList{
391 Message: []*pb.MessageList_Message{
392 nil,
393 &pb.MessageList_Message{
394 Name: proto.String("Horse"),
395 },
396 nil,
397 },
398 }
399 want := `Message <nil>
400Message {
401 name: "Horse"
402}
403Message <nil>
404`
405 if s := proto.MarshalTextString(m); s != want {
406 t.Errorf(" got: %s\nwant: %s", s, want)
407 }
408}