Home | History | Annotate | Download | only in proto
      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 
     32 package proto_test
     33 
     34 import (
     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.
     50 type textMessage struct {
     51 }
     52 
     53 func (*textMessage) MarshalText() ([]byte, error) {
     54 	return []byte("custom"), nil
     55 }
     56 
     57 func (*textMessage) UnmarshalText(bytes []byte) error {
     58 	if string(bytes) != "custom" {
     59 		return errors.New("expected 'custom'")
     60 	}
     61 	return nil
     62 }
     63 
     64 func (*textMessage) Reset()         {}
     65 func (*textMessage) String() string { return "" }
     66 func (*textMessage) ProtoMessage()  {}
     67 
     68 func 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 
    126 const text = `count: 42
    127 name: "Dave"
    128 quote: "\"I didn't want to go.\""
    129 pet: "bunny"
    130 pet: "kitty"
    131 pet: "horsey"
    132 inner: <
    133   host: "footrest.syd"
    134   port: 7001
    135   connected: true
    136 >
    137 others: <
    138   key: 3735928559
    139   value: "\001A\007\014"
    140 >
    141 others: <
    142   weight: 6.022
    143   inner: <
    144     host: "lesha.mtv"
    145     port: 8002
    146   >
    147 >
    148 bikeshed: BLUE
    149 SomeGroup {
    150   group_field: 8
    151 }
    152 /* 2 unknown bytes */
    153 13: 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 */
    161 201: "\t3G skiing"
    162 /* 3 unknown bytes */
    163 202: 19
    164 `
    165 
    166 func 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 
    177 func 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 }
    187 func 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 
    201 func 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 
    211 func 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 
    235 func 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 
    244 func 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 
    252 func 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 
    297 var compactText = compact(text)
    298 
    299 func 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 
    306 func 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.
    356 type limitedWriter struct {
    357 	b     bytes.Buffer
    358 	limit int
    359 }
    360 
    361 var outOfSpace = errors.New("proto: insufficient space")
    362 
    363 func (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 
    375 func 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 
    393 func 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 
    414 func 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>
    425 Message {
    426   name: "Horse"
    427 }
    428 Message <nil>
    429 `
    430 	if s := proto.MarshalTextString(m); s != want {
    431 		t.Errorf(" got: %s\nwant: %s", s, want)
    432 	}
    433 }
    434 
    435 func 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