blob: e3bb79169c187f977c67a5e3bbc5a22dd8b3ceed [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 Symonds1d72f7a2011-08-19 18:28:52 +100038 "strings"
Rob Pikeaaa3a622010-03-20 22:32:34 -070039 "testing"
David Symondsaa922ff2011-07-19 14:58:06 +100040
Rob Pike3f6f2d82011-12-18 13:55:35 -080041 "code.google.com/p/goprotobuf/proto"
David Symondsaa922ff2011-07-19 14:58:06 +100042
David Symonds704096f2012-03-15 15:10:26 +110043 pb "./testdata"
Rob Pikeaaa3a622010-03-20 22:32:34 -070044)
45
David Symondsaa922ff2011-07-19 14:58:06 +100046func newTestMessage() *pb.MyMessage {
47 msg := &pb.MyMessage{
48 Count: proto.Int32(42),
49 Name: proto.String("Dave"),
50 Quote: proto.String(`"I didn't want to go."`),
Rob Pike9caa5b92010-05-11 16:04:57 -070051 Pet: []string{"bunny", "kitty", "horsey"},
David Symondsaa922ff2011-07-19 14:58:06 +100052 Inner: &pb.InnerMessage{
53 Host: proto.String("footrest.syd"),
54 Port: proto.Int32(7001),
55 Connected: proto.Bool(true),
Rob Pikeaaa3a622010-03-20 22:32:34 -070056 },
David Symondsaa922ff2011-07-19 14:58:06 +100057 Others: []*pb.OtherMessage{
David Symonds92dd6c12012-03-23 10:59:49 +110058 {
David Symondsaa922ff2011-07-19 14:58:06 +100059 Key: proto.Int64(0xdeadbeef),
Rob Pikeaaa3a622010-03-20 22:32:34 -070060 Value: []byte{1, 65, 7, 12},
61 },
David Symonds92dd6c12012-03-23 10:59:49 +110062 {
David Symondsaa922ff2011-07-19 14:58:06 +100063 Weight: proto.Float32(6.022),
64 Inner: &pb.InnerMessage{
65 Host: proto.String("lesha.mtv"),
66 Port: proto.Int32(8002),
Rob Pikeaaa3a622010-03-20 22:32:34 -070067 },
68 },
69 },
David Symondsefeca9a2012-05-08 10:36:04 +100070 Bikeshed: pb.MyMessage_BLUE.Enum(),
David Symondsaa922ff2011-07-19 14:58:06 +100071 Somegroup: &pb.MyMessage_SomeGroup{
72 GroupField: proto.Int32(8),
David Symonds9f402812011-04-28 18:08:44 +100073 },
David Symonds1d72f7a2011-08-19 18:28:52 +100074 // One normally wouldn't do this.
75 // This is an undeclared tag 13, as a varint (wire type 0) with value 4.
76 XXX_unrecognized: []byte{13<<3 | 0, 4},
Rob Pikeaaa3a622010-03-20 22:32:34 -070077 }
David Symondsaa922ff2011-07-19 14:58:06 +100078 ext := &pb.Ext{
79 Data: proto.String("Big gobs for big rats"),
David Symondse37856c2011-06-22 12:52:53 +100080 }
David Symondsaa922ff2011-07-19 14:58:06 +100081 if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil {
David Symondse37856c2011-06-22 12:52:53 +100082 panic(err)
83 }
David Symonds61826da2012-05-05 09:31:28 +100084 greetings := []string{"adg", "easy", "cow"}
85 if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil {
86 panic(err)
87 }
David Symonds1d72f7a2011-08-19 18:28:52 +100088
89 // Add an unknown extension. We marshal a pb.Ext, and fake the ID.
90 b, err := proto.Marshal(&pb.Ext{Data: proto.String("3G skiing")})
91 if err != nil {
92 panic(err)
93 }
David Symonds54531052011-12-08 12:00:31 +110094 b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...)
95 proto.SetRawExtension(msg, 201, b)
David Symonds1d72f7a2011-08-19 18:28:52 +100096
97 // Extensions can be plain fields, too, so let's test that.
David Symonds54531052011-12-08 12:00:31 +110098 b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19)
99 proto.SetRawExtension(msg, 202, b)
David Symonds1d72f7a2011-08-19 18:28:52 +1000100
David Symondse37856c2011-06-22 12:52:53 +1000101 return msg
Rob Pikeaaa3a622010-03-20 22:32:34 -0700102}
103
104const text = `count: 42
105name: "Dave"
106quote: "\"I didn't want to go.\""
107pet: "bunny"
108pet: "kitty"
109pet: "horsey"
110inner: <
111 host: "footrest.syd"
112 port: 7001
113 connected: true
114>
115others: <
116 key: 3735928559
David Symonds4c95bfe2011-09-13 14:43:27 +1000117 value: "\001A\007\014"
Rob Pikeaaa3a622010-03-20 22:32:34 -0700118>
119others: <
120 weight: 6.022
121 inner: <
122 host: "lesha.mtv"
123 port: 8002
124 >
125>
126bikeshed: BLUE
David Symonds9f402812011-04-28 18:08:44 +1000127SomeGroup {
128 group_field: 8
129}
David Symonds1d72f7a2011-08-19 18:28:52 +1000130/* 2 unknown bytes */
David Symonds20370902013-03-23 17:20:01 +110013113: 4
Rob Pikeb7907bf2012-02-13 14:25:20 +1100132[testdata.Ext.more]: <
David Symondse37856c2011-06-22 12:52:53 +1000133 data: "Big gobs for big rats"
134>
David Symonds61826da2012-05-05 09:31:28 +1000135[testdata.greeting]: "adg"
136[testdata.greeting]: "easy"
137[testdata.greeting]: "cow"
David Symonds1d72f7a2011-08-19 18:28:52 +1000138/* 13 unknown bytes */
David Symonds20370902013-03-23 17:20:01 +1100139201: "\t3G skiing"
David Symonds1d72f7a2011-08-19 18:28:52 +1000140/* 3 unknown bytes */
David Symonds20370902013-03-23 17:20:01 +1100141202: 19
Rob Pikeaaa3a622010-03-20 22:32:34 -0700142`
143
David Symonds4dc589e2012-12-06 14:05:48 +1100144func TestMarshalText(t *testing.T) {
Rob Pikeaaa3a622010-03-20 22:32:34 -0700145 buf := new(bytes.Buffer)
David Symonds4dc589e2012-12-06 14:05:48 +1100146 if err := proto.MarshalText(buf, newTestMessage()); err != nil {
147 t.Fatalf("proto.MarshalText: %v", err)
148 }
Rob Pikeaaa3a622010-03-20 22:32:34 -0700149 s := buf.String()
150 if s != text {
151 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, text)
152 }
153}
154
David Symonds6e8ab872013-01-30 17:07:26 +1100155func TestMarshalTextNil(t *testing.T) {
156 want := "<nil>"
157 tests := []proto.Message{nil, (*pb.MyMessage)(nil)}
158 for i, test := range tests {
159 buf := new(bytes.Buffer)
160 if err := proto.MarshalText(buf, test); err != nil {
161 t.Fatal(err)
162 }
163 if got := buf.String(); got != want {
164 t.Errorf("%d: got %q want %q", i, got, want)
165 }
166 }
167}
168
David Symondsf8a1fcc2013-05-03 08:51:23 +1000169func TestMarshalTextUnknownEnum(t *testing.T) {
170 // The Color enum only specifies values 0-2.
171 m := &pb.MyMessage{Bikeshed: pb.MyMessage_Color(3).Enum()}
172 got := m.String()
173 const want = `bikeshed:3 `
174 if got != want {
175 t.Errorf("\n got %q\nwant %q", got, want)
176 }
177}
178
David Symonds4dc589e2012-12-06 14:05:48 +1100179func BenchmarkMarshalTextBuffered(b *testing.B) {
David Symondsdbdc4212012-11-08 08:20:35 +1100180 buf := new(bytes.Buffer)
181 m := newTestMessage()
182 for i := 0; i < b.N; i++ {
183 buf.Reset()
184 proto.MarshalText(buf, m)
185 }
186}
187
David Symonds4dc589e2012-12-06 14:05:48 +1100188func BenchmarkMarshalTextUnbuffered(b *testing.B) {
189 w := ioutil.Discard
190 m := newTestMessage()
191 for i := 0; i < b.N; i++ {
192 proto.MarshalText(w, m)
193 }
194}
195
Rob Pikeaaa3a622010-03-20 22:32:34 -0700196func compact(src string) string {
David Symondse37856c2011-06-22 12:52:53 +1000197 // s/[ \n]+/ /g; s/ $//;
Rob Pikeaaa3a622010-03-20 22:32:34 -0700198 dst := make([]byte, len(src))
David Symonds1d72f7a2011-08-19 18:28:52 +1000199 space, comment := false, false
Rob Pikeaaa3a622010-03-20 22:32:34 -0700200 j := 0
201 for i := 0; i < len(src); i++ {
David Symonds1d72f7a2011-08-19 18:28:52 +1000202 if strings.HasPrefix(src[i:], "/*") {
203 comment = true
204 i++
205 continue
206 }
207 if comment && strings.HasPrefix(src[i:], "*/") {
208 comment = false
209 i++
210 continue
211 }
212 if comment {
213 continue
214 }
Rob Pikeaaa3a622010-03-20 22:32:34 -0700215 c := src[i]
216 if c == ' ' || c == '\n' {
217 space = true
218 continue
219 }
David Symonds9f402812011-04-28 18:08:44 +1000220 if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') {
221 space = false
222 }
223 if c == '{' {
Rob Pikeaaa3a622010-03-20 22:32:34 -0700224 space = false
225 }
226 if space {
227 dst[j] = ' '
228 j++
229 space = false
230 }
231 dst[j] = c
232 j++
233 }
234 if space {
235 dst[j] = ' '
236 j++
237 }
238 return string(dst[0:j])
239}
240
241var compactText = compact(text)
242
243func TestCompactText(t *testing.T) {
David Symondsaa922ff2011-07-19 14:58:06 +1000244 s := proto.CompactTextString(newTestMessage())
Rob Pikeaaa3a622010-03-20 22:32:34 -0700245 if s != compactText {
David Symonds4c95bfe2011-09-13 14:43:27 +1000246 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v\n===\n", s, compactText)
247 }
248}
249
250func TestStringEscaping(t *testing.T) {
251 testCases := []struct {
252 in *pb.Strings
253 out string
254 }{
255 {
256 // Test data from C++ test (TextFormatTest.StringEscape).
257 // Single divergence: we don't escape apostrophes.
258 &pb.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces")},
259 "string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"\n",
260 },
261 {
262 // Test data from the same C++ test.
263 &pb.Strings{StringField: proto.String("\350\260\267\346\255\214")},
264 "string_field: \"\\350\\260\\267\\346\\255\\214\"\n",
265 },
David Symondsfa94a1e2012-09-24 13:21:49 +1000266 {
267 // Some UTF-8.
268 &pb.Strings{StringField: proto.String("\x00\x01\xff\x81")},
269 `string_field: "\000\001\377\201"` + "\n",
270 },
David Symonds4c95bfe2011-09-13 14:43:27 +1000271 }
272
273 for i, tc := range testCases {
274 var buf bytes.Buffer
David Symonds4dc589e2012-12-06 14:05:48 +1100275 if err := proto.MarshalText(&buf, tc.in); err != nil {
276 t.Errorf("proto.MarsalText: %v", err)
277 continue
278 }
David Symondsfa94a1e2012-09-24 13:21:49 +1000279 s := buf.String()
280 if s != tc.out {
David Symonds4c95bfe2011-09-13 14:43:27 +1000281 t.Errorf("#%d: Got:\n%s\nExpected:\n%s\n", i, s, tc.out)
David Symondsfa94a1e2012-09-24 13:21:49 +1000282 continue
283 }
284
285 // Check round-trip.
286 pb := new(pb.Strings)
287 if err := proto.UnmarshalText(s, pb); err != nil {
288 t.Errorf("#%d: UnmarshalText: %v", i, err)
289 continue
290 }
291 if !proto.Equal(pb, tc.in) {
292 t.Errorf("#%d: Round-trip failed:\nstart: %v\n end: %v", i, tc.in, pb)
David Symonds4c95bfe2011-09-13 14:43:27 +1000293 }
Rob Pikeaaa3a622010-03-20 22:32:34 -0700294 }
295}
David Symonds4dc589e2012-12-06 14:05:48 +1100296
297// A limitedWriter accepts some output before it fails.
298// This is a proxy for something like a nearly-full or imminently-failing disk,
299// or a network connection that is about to die.
300type limitedWriter struct {
301 b bytes.Buffer
302 limit int
303}
304
305var outOfSpace = errors.New("insufficient space")
306
307func (w *limitedWriter) Write(p []byte) (n int, err error) {
308 var avail = w.limit - w.b.Len()
309 if avail <= 0 {
310 return 0, outOfSpace
311 }
312 if len(p) <= avail {
313 return w.b.Write(p)
314 }
315 n, _ = w.b.Write(p[:avail])
316 return n, outOfSpace
317}
318
319func TestMarshalTextFailing(t *testing.T) {
320 // Try lots of different sizes to exercise more error code-paths.
321 for lim := 0; lim < len(text); lim++ {
322 buf := new(limitedWriter)
323 buf.limit = lim
324 err := proto.MarshalText(buf, newTestMessage())
325 // We expect a certain error, but also some partial results in the buffer.
326 if err != outOfSpace {
327 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", err, outOfSpace)
328 }
329 s := buf.b.String()
330 x := text[:buf.limit]
331 if s != x {
332 t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, x)
333 }
334 }
335}