...

Source file src/github.com/gogo/protobuf/proto/text_test.go

Documentation: github.com/gogo/protobuf/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  	"sync"
    41  	"testing"
    42  
    43  	"github.com/gogo/protobuf/proto"
    44  
    45  	proto3pb "github.com/gogo/protobuf/proto/proto3_proto"
    46  	pb "github.com/gogo/protobuf/proto/test_proto"
    47  	"github.com/gogo/protobuf/types"
    48  )
    49  
    50  // textMessage implements the methods that allow it to marshal and unmarshal
    51  // itself as text.
    52  type textMessage struct {
    53  }
    54  
    55  func (*textMessage) MarshalText() ([]byte, error) {
    56  	return []byte("custom"), nil
    57  }
    58  
    59  func (*textMessage) UnmarshalText(bytes []byte) error {
    60  	if string(bytes) != "custom" {
    61  		return errors.New("expected 'custom'")
    62  	}
    63  	return nil
    64  }
    65  
    66  func (*textMessage) Reset()         {}
    67  func (*textMessage) String() string { return "" }
    68  func (*textMessage) ProtoMessage()  {}
    69  
    70  func newTestMessage() *pb.MyMessage {
    71  	msg := &pb.MyMessage{
    72  		Count: proto.Int32(42),
    73  		Name:  proto.String("Dave"),
    74  		Quote: proto.String(`"I didn't want to go."`),
    75  		Pet:   []string{"bunny", "kitty", "horsey"},
    76  		Inner: &pb.InnerMessage{
    77  			Host:      proto.String("footrest.syd"),
    78  			Port:      proto.Int32(7001),
    79  			Connected: proto.Bool(true),
    80  		},
    81  		Others: []*pb.OtherMessage{
    82  			{
    83  				Key:   proto.Int64(0xdeadbeef),
    84  				Value: []byte{1, 65, 7, 12},
    85  			},
    86  			{
    87  				Weight: proto.Float32(6.022),
    88  				Inner: &pb.InnerMessage{
    89  					Host: proto.String("lesha.mtv"),
    90  					Port: proto.Int32(8002),
    91  				},
    92  			},
    93  		},
    94  		Bikeshed: pb.MyMessage_BLUE.Enum(),
    95  		Somegroup: &pb.MyMessage_SomeGroup{
    96  			GroupField: proto.Int32(8),
    97  		},
    98  		// One normally wouldn't do this.
    99  		// This is an undeclared tag 13, as a varint (wire type 0) with value 4.
   100  		XXX_unrecognized: []byte{13<<3 | 0, 4},
   101  	}
   102  	ext := &pb.Ext{
   103  		Data: proto.String("Big gobs for big rats"),
   104  	}
   105  	if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil {
   106  		panic(err)
   107  	}
   108  	greetings := []string{"adg", "easy", "cow"}
   109  	if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil {
   110  		panic(err)
   111  	}
   112  
   113  	// Add an unknown extension. We marshal a pb.Ext, and fake the ID.
   114  	b, err := proto.Marshal(&pb.Ext{Data: proto.String("3G skiing")})
   115  	if err != nil {
   116  		panic(err)
   117  	}
   118  	b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...)
   119  	proto.SetRawExtension(msg, 201, b)
   120  
   121  	// Extensions can be plain fields, too, so let's test that.
   122  	b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19)
   123  	proto.SetRawExtension(msg, 202, b)
   124  
   125  	return msg
   126  }
   127  
   128  const text = `count: 42
   129  name: "Dave"
   130  quote: "\"I didn't want to go.\""
   131  pet: "bunny"
   132  pet: "kitty"
   133  pet: "horsey"
   134  inner: <
   135    host: "footrest.syd"
   136    port: 7001
   137    connected: true
   138  >
   139  others: <
   140    key: 3735928559
   141    value: "\001A\007\014"
   142  >
   143  others: <
   144    weight: 6.022
   145    inner: <
   146      host: "lesha.mtv"
   147      port: 8002
   148    >
   149  >
   150  bikeshed: BLUE
   151  SomeGroup {
   152    group_field: 8
   153  }
   154  /* 2 unknown bytes */
   155  13: 4
   156  [test_proto.Ext.more]: <
   157    data: "Big gobs for big rats"
   158  >
   159  [test_proto.greeting]: "adg"
   160  [test_proto.greeting]: "easy"
   161  [test_proto.greeting]: "cow"
   162  /* 13 unknown bytes */
   163  201: "\t3G skiing"
   164  /* 3 unknown bytes */
   165  202: 19
   166  `
   167  
   168  func TestMarshalText(t *testing.T) {
   169  	buf := new(bytes.Buffer)
   170  	if err := proto.MarshalText(buf, newTestMessage()); err != nil {
   171  		t.Fatalf("proto.MarshalText: %v", err)
   172  	}
   173  	s := buf.String()
   174  	if s != text {
   175  		t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, text)
   176  	}
   177  }
   178  
   179  func TestMarshalTextCustomMessage(t *testing.T) {
   180  	buf := new(bytes.Buffer)
   181  	if err := proto.MarshalText(buf, &textMessage{}); err != nil {
   182  		t.Fatalf("proto.MarshalText: %v", err)
   183  	}
   184  	s := buf.String()
   185  	if s != "custom" {
   186  		t.Errorf("Got %q, expected %q", s, "custom")
   187  	}
   188  }
   189  func TestMarshalTextNil(t *testing.T) {
   190  	want := "<nil>"
   191  	tests := []proto.Message{nil, (*pb.MyMessage)(nil)}
   192  	for i, test := range tests {
   193  		buf := new(bytes.Buffer)
   194  		if err := proto.MarshalText(buf, test); err != nil {
   195  			t.Fatal(err)
   196  		}
   197  		if got := buf.String(); got != want {
   198  			t.Errorf("%d: got %q want %q", i, got, want)
   199  		}
   200  	}
   201  }
   202  
   203  func TestMarshalTextUnknownEnum(t *testing.T) {
   204  	// The Color enum only specifies values 0-2.
   205  	m := &pb.MyMessage{Bikeshed: pb.MyMessage_Color(3).Enum()}
   206  	got := m.String()
   207  	const want = `bikeshed:3 `
   208  	if got != want {
   209  		t.Errorf("\n got %q\nwant %q", got, want)
   210  	}
   211  }
   212  
   213  func TestTextOneof(t *testing.T) {
   214  	tests := []struct {
   215  		m    proto.Message
   216  		want string
   217  	}{
   218  		// zero message
   219  		{&pb.Communique{}, ``},
   220  		// scalar field
   221  		{&pb.Communique{Union: &pb.Communique_Number{Number: 4}}, `number:4`},
   222  		// message field
   223  		{&pb.Communique{Union: &pb.Communique_Msg{
   224  			Msg: &pb.Strings{StringField: proto.String("why hello!")},
   225  		}}, `msg:<string_field:"why hello!" >`},
   226  		// bad oneof (should not panic)
   227  		{&pb.Communique{Union: &pb.Communique_Msg{Msg: nil}}, `msg:/* nil */`},
   228  	}
   229  	for _, test := range tests {
   230  		got := strings.TrimSpace(test.m.String())
   231  		if got != test.want {
   232  			t.Errorf("\n got %s\nwant %s", got, test.want)
   233  		}
   234  	}
   235  }
   236  
   237  func BenchmarkMarshalTextBuffered(b *testing.B) {
   238  	buf := new(bytes.Buffer)
   239  	m := newTestMessage()
   240  	for i := 0; i < b.N; i++ {
   241  		buf.Reset()
   242  		proto.MarshalText(buf, m)
   243  	}
   244  }
   245  
   246  func BenchmarkMarshalTextUnbuffered(b *testing.B) {
   247  	w := ioutil.Discard
   248  	m := newTestMessage()
   249  	for i := 0; i < b.N; i++ {
   250  		proto.MarshalText(w, m)
   251  	}
   252  }
   253  
   254  func compact(src string) string {
   255  	// s/[ \n]+/ /g; s/ $//;
   256  	dst := make([]byte, len(src))
   257  	space, comment := false, false
   258  	j := 0
   259  	for i := 0; i < len(src); i++ {
   260  		if strings.HasPrefix(src[i:], "/*") {
   261  			comment = true
   262  			i++
   263  			continue
   264  		}
   265  		if comment && strings.HasPrefix(src[i:], "*/") {
   266  			comment = false
   267  			i++
   268  			continue
   269  		}
   270  		if comment {
   271  			continue
   272  		}
   273  		c := src[i]
   274  		if c == ' ' || c == '\n' {
   275  			space = true
   276  			continue
   277  		}
   278  		if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') {
   279  			space = false
   280  		}
   281  		if c == '{' {
   282  			space = false
   283  		}
   284  		if space {
   285  			dst[j] = ' '
   286  			j++
   287  			space = false
   288  		}
   289  		dst[j] = c
   290  		j++
   291  	}
   292  	if space {
   293  		dst[j] = ' '
   294  		j++
   295  	}
   296  	return string(dst[0:j])
   297  }
   298  
   299  var compactText = compact(text)
   300  
   301  func TestCompactText(t *testing.T) {
   302  	s := proto.CompactTextString(newTestMessage())
   303  	if s != compactText {
   304  		t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v\n===\n", s, compactText)
   305  	}
   306  }
   307  
   308  func TestStringEscaping(t *testing.T) {
   309  	testCases := []struct {
   310  		in  *pb.Strings
   311  		out string
   312  	}{
   313  		{
   314  			// Test data from C++ test (TextFormatTest.StringEscape).
   315  			// Single divergence: we don't escape apostrophes.
   316  			&pb.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and  multiple   spaces")},
   317  			"string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and  multiple   spaces\"\n",
   318  		},
   319  		{
   320  			// Test data from the same C++ test.
   321  			&pb.Strings{StringField: proto.String("\350\260\267\346\255\214")},
   322  			"string_field: \"\\350\\260\\267\\346\\255\\214\"\n",
   323  		},
   324  		{
   325  			// Some UTF-8.
   326  			&pb.Strings{StringField: proto.String("\x00\x01\xff\x81")},
   327  			`string_field: "\000\001\377\201"` + "\n",
   328  		},
   329  	}
   330  
   331  	for i, tc := range testCases {
   332  		var buf bytes.Buffer
   333  		if err := proto.MarshalText(&buf, tc.in); err != nil {
   334  			t.Errorf("proto.MarsalText: %v", err)
   335  			continue
   336  		}
   337  		s := buf.String()
   338  		if s != tc.out {
   339  			t.Errorf("#%d: Got:\n%s\nExpected:\n%s\n", i, s, tc.out)
   340  			continue
   341  		}
   342  
   343  		// Check round-trip.
   344  		pbStrings := new(pb.Strings)
   345  		if err := proto.UnmarshalText(s, pbStrings); err != nil {
   346  			t.Errorf("#%d: UnmarshalText: %v", i, err)
   347  			continue
   348  		}
   349  		if !proto.Equal(pbStrings, tc.in) {
   350  			t.Errorf("#%d: Round-trip failed:\nstart: %v\n  end: %v", i, tc.in, pbStrings)
   351  		}
   352  	}
   353  }
   354  
   355  // A limitedWriter accepts some output before it fails.
   356  // This is a proxy for something like a nearly-full or imminently-failing disk,
   357  // or a network connection that is about to die.
   358  type limitedWriter struct {
   359  	b     bytes.Buffer
   360  	limit int
   361  }
   362  
   363  var outOfSpace = errors.New("proto: insufficient space")
   364  
   365  func (w *limitedWriter) Write(p []byte) (n int, err error) {
   366  	var avail = w.limit - w.b.Len()
   367  	if avail <= 0 {
   368  		return 0, outOfSpace
   369  	}
   370  	if len(p) <= avail {
   371  		return w.b.Write(p)
   372  	}
   373  	n, _ = w.b.Write(p[:avail])
   374  	return n, outOfSpace
   375  }
   376  
   377  func TestMarshalTextFailing(t *testing.T) {
   378  	// Try lots of different sizes to exercise more error code-paths.
   379  	for lim := 0; lim < len(text); lim++ {
   380  		buf := new(limitedWriter)
   381  		buf.limit = lim
   382  		err := proto.MarshalText(buf, newTestMessage())
   383  		// We expect a certain error, but also some partial results in the buffer.
   384  		if err != outOfSpace {
   385  			t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", err, outOfSpace)
   386  		}
   387  		s := buf.b.String()
   388  		x := text[:buf.limit]
   389  		if s != x {
   390  			t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, x)
   391  		}
   392  	}
   393  }
   394  
   395  func TestFloats(t *testing.T) {
   396  	tests := []struct {
   397  		f    float64
   398  		want string
   399  	}{
   400  		{0, "0"},
   401  		{4.7, "4.7"},
   402  		{math.Inf(1), "inf"},
   403  		{math.Inf(-1), "-inf"},
   404  		{math.NaN(), "nan"},
   405  	}
   406  	for _, test := range tests {
   407  		msg := &pb.FloatingPoint{F: &test.f}
   408  		got := strings.TrimSpace(msg.String())
   409  		want := `f:` + test.want
   410  		if got != want {
   411  			t.Errorf("f=%f: got %q, want %q", test.f, got, want)
   412  		}
   413  	}
   414  }
   415  
   416  func TestRepeatedNilText(t *testing.T) {
   417  	m := &pb.MessageList{
   418  		Message: []*pb.MessageList_Message{
   419  			nil,
   420  			{
   421  				Name: proto.String("Horse"),
   422  			},
   423  			nil,
   424  		},
   425  	}
   426  	want := `Message <nil>
   427  Message {
   428    name: "Horse"
   429  }
   430  Message <nil>
   431  `
   432  	if s := proto.MarshalTextString(m); s != want {
   433  		t.Errorf(" got: %s\nwant: %s", s, want)
   434  	}
   435  }
   436  
   437  func TestProto3Text(t *testing.T) {
   438  	tests := []struct {
   439  		m    proto.Message
   440  		want string
   441  	}{
   442  		// zero message
   443  		{&proto3pb.Message{}, ``},
   444  		// zero message except for an empty byte slice
   445  		{&proto3pb.Message{Data: []byte{}}, ``},
   446  		// trivial case
   447  		{&proto3pb.Message{Name: "Rob", HeightInCm: 175}, `name:"Rob" height_in_cm:175`},
   448  		// empty map
   449  		{&pb.MessageWithMap{}, ``},
   450  		// non-empty map; map format is the same as a repeated struct,
   451  		// and they are sorted by key (numerically for numeric keys).
   452  		{
   453  			&pb.MessageWithMap{NameMapping: map[int32]string{
   454  				-1:      "Negatory",
   455  				7:       "Lucky",
   456  				1234:    "Feist",
   457  				6345789: "Otis",
   458  			}},
   459  			`name_mapping:<key:-1 value:"Negatory" > ` +
   460  				`name_mapping:<key:7 value:"Lucky" > ` +
   461  				`name_mapping:<key:1234 value:"Feist" > ` +
   462  				`name_mapping:<key:6345789 value:"Otis" >`,
   463  		},
   464  		// map with nil value; not well-defined, but we shouldn't crash
   465  		{
   466  			&pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{7: nil}},
   467  			`msg_mapping:<key:7 >`,
   468  		},
   469  	}
   470  	for _, test := range tests {
   471  		got := strings.TrimSpace(test.m.String())
   472  		if got != test.want {
   473  			t.Errorf("\n got %s\nwant %s", got, test.want)
   474  		}
   475  	}
   476  }
   477  
   478  func TestRacyMarshal(t *testing.T) {
   479  	// This test should be run with the race detector.
   480  
   481  	any := &pb.MyMessage{Count: proto.Int32(47), Name: proto.String("David")}
   482  	proto.SetExtension(any, pb.E_Ext_Text, proto.String("bar"))
   483  	b, err := proto.Marshal(any)
   484  	if err != nil {
   485  		panic(err)
   486  	}
   487  	m := &proto3pb.Message{
   488  		Name:        "David",
   489  		ResultCount: 47,
   490  		Anything:    &types.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any), Value: b},
   491  	}
   492  
   493  	wantText := proto.MarshalTextString(m)
   494  	wantBytes, err := proto.Marshal(m)
   495  	if err != nil {
   496  		t.Fatalf("proto.Marshal error: %v", err)
   497  	}
   498  
   499  	var wg sync.WaitGroup
   500  	defer wg.Wait()
   501  	wg.Add(20)
   502  	for i := 0; i < 10; i++ {
   503  		go func() {
   504  			defer wg.Done()
   505  			got := proto.MarshalTextString(m)
   506  			if got != wantText {
   507  				t.Errorf("proto.MarshalTextString = %q, want %q", got, wantText)
   508  			}
   509  		}()
   510  		go func() {
   511  			defer wg.Done()
   512  			got, err := proto.Marshal(m)
   513  			if !bytes.Equal(got, wantBytes) || err != nil {
   514  				t.Errorf("proto.Marshal = (%x, %v), want (%x, nil)", got, err, wantBytes)
   515  			}
   516  		}()
   517  	}
   518  }
   519  

View as plain text