...

Source file src/google.golang.org/protobuf/internal/encoding/text/encode_test.go

Documentation: google.golang.org/protobuf/internal/encoding/text

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package text_test
     6  
     7  import (
     8  	"math"
     9  	"strings"
    10  	"testing"
    11  	"unicode/utf8"
    12  
    13  	"github.com/google/go-cmp/cmp"
    14  
    15  	"google.golang.org/protobuf/internal/detrand"
    16  	"google.golang.org/protobuf/internal/encoding/text"
    17  )
    18  
    19  // Disable detrand to enable direct comparisons on outputs.
    20  func init() { detrand.Disable() }
    21  
    22  func TestEncoder(t *testing.T) {
    23  	tests := []encoderTestCase{
    24  		{
    25  			desc:          "no-opt",
    26  			write:         func(e *text.Encoder) {},
    27  			wantOut:       ``,
    28  			wantOutIndent: ``,
    29  		},
    30  		{
    31  			desc: "true",
    32  			write: func(e *text.Encoder) {
    33  				e.WriteName("bool")
    34  				e.WriteBool(true)
    35  			},
    36  			wantOut:       `bool:true`,
    37  			wantOutIndent: `bool: true`,
    38  		},
    39  		{
    40  			desc: "false",
    41  			write: func(e *text.Encoder) {
    42  				e.WriteName("bool")
    43  				e.WriteBool(false)
    44  			},
    45  			wantOut:       `bool:false`,
    46  			wantOutIndent: `bool: false`,
    47  		},
    48  		{
    49  			desc: "bracket name",
    50  			write: func(e *text.Encoder) {
    51  				e.WriteName("[extension]")
    52  				e.WriteString("hello")
    53  			},
    54  			wantOut:       `[extension]:"hello"`,
    55  			wantOutIndent: `[extension]: "hello"`,
    56  		},
    57  		{
    58  			desc: "numeric name",
    59  			write: func(e *text.Encoder) {
    60  				e.WriteName("01234")
    61  				e.WriteString("hello")
    62  			},
    63  			wantOut:       `01234:"hello"`,
    64  			wantOutIndent: `01234: "hello"`,
    65  		},
    66  		{
    67  			desc: "string",
    68  			write: func(e *text.Encoder) {
    69  				e.WriteName("str")
    70  				e.WriteString("hello world")
    71  			},
    72  			wantOut:       `str:"hello world"`,
    73  			wantOutIndent: `str: "hello world"`,
    74  		},
    75  		{
    76  			desc: "enum",
    77  			write: func(e *text.Encoder) {
    78  				e.WriteName("enum")
    79  				e.WriteLiteral("ENUM_VALUE")
    80  			},
    81  			wantOut:       `enum:ENUM_VALUE`,
    82  			wantOutIndent: `enum: ENUM_VALUE`,
    83  		},
    84  		{
    85  			desc: "float64",
    86  			write: func(e *text.Encoder) {
    87  				e.WriteName("float64")
    88  				e.WriteFloat(1.0199999809265137, 64)
    89  			},
    90  			wantOut:       `float64:1.0199999809265137`,
    91  			wantOutIndent: `float64: 1.0199999809265137`,
    92  		},
    93  		{
    94  			desc: "float64 max value",
    95  			write: func(e *text.Encoder) {
    96  				e.WriteName("float64")
    97  				e.WriteFloat(math.MaxFloat64, 64)
    98  			},
    99  			wantOut:       `float64:1.7976931348623157e+308`,
   100  			wantOutIndent: `float64: 1.7976931348623157e+308`,
   101  		},
   102  		{
   103  			desc: "float64 min value",
   104  			write: func(e *text.Encoder) {
   105  				e.WriteName("float64")
   106  				e.WriteFloat(-math.MaxFloat64, 64)
   107  			},
   108  			wantOut:       `float64:-1.7976931348623157e+308`,
   109  			wantOutIndent: `float64: -1.7976931348623157e+308`,
   110  		},
   111  		{
   112  			desc: "float64 nan",
   113  			write: func(e *text.Encoder) {
   114  				e.WriteName("float64")
   115  				e.WriteFloat(math.NaN(), 64)
   116  			},
   117  			wantOut:       `float64:nan`,
   118  			wantOutIndent: `float64: nan`,
   119  		},
   120  		{
   121  			desc: "float64 inf",
   122  			write: func(e *text.Encoder) {
   123  				e.WriteName("float64")
   124  				e.WriteFloat(math.Inf(+1), 64)
   125  			},
   126  			wantOut:       `float64:inf`,
   127  			wantOutIndent: `float64: inf`,
   128  		},
   129  		{
   130  			desc: "float64 -inf",
   131  			write: func(e *text.Encoder) {
   132  				e.WriteName("float64")
   133  				e.WriteFloat(math.Inf(-1), 64)
   134  			},
   135  			wantOut:       `float64:-inf`,
   136  			wantOutIndent: `float64: -inf`,
   137  		},
   138  		{
   139  			desc: "float64 negative zero",
   140  			write: func(e *text.Encoder) {
   141  				e.WriteName("float64")
   142  				e.WriteFloat(math.Copysign(0, -1), 64)
   143  			},
   144  			wantOut:       `float64:-0`,
   145  			wantOutIndent: `float64: -0`,
   146  		},
   147  		{
   148  			desc: "float32",
   149  			write: func(e *text.Encoder) {
   150  				e.WriteName("float")
   151  				e.WriteFloat(1.02, 32)
   152  			},
   153  			wantOut:       `float:1.02`,
   154  			wantOutIndent: `float: 1.02`,
   155  		},
   156  		{
   157  			desc: "float32 max value",
   158  			write: func(e *text.Encoder) {
   159  				e.WriteName("float32")
   160  				e.WriteFloat(math.MaxFloat32, 32)
   161  			},
   162  			wantOut:       `float32:3.4028235e+38`,
   163  			wantOutIndent: `float32: 3.4028235e+38`,
   164  		},
   165  		{
   166  			desc: "float32 nan",
   167  			write: func(e *text.Encoder) {
   168  				e.WriteName("float32")
   169  				e.WriteFloat(math.NaN(), 32)
   170  			},
   171  			wantOut:       `float32:nan`,
   172  			wantOutIndent: `float32: nan`,
   173  		},
   174  		{
   175  			desc: "float32 inf",
   176  			write: func(e *text.Encoder) {
   177  				e.WriteName("float32")
   178  				e.WriteFloat(math.Inf(+1), 32)
   179  			},
   180  			wantOut:       `float32:inf`,
   181  			wantOutIndent: `float32: inf`,
   182  		},
   183  		{
   184  			desc: "float32 -inf",
   185  			write: func(e *text.Encoder) {
   186  				e.WriteName("float32")
   187  				e.WriteFloat(math.Inf(-1), 32)
   188  			},
   189  			wantOut:       `float32:-inf`,
   190  			wantOutIndent: `float32: -inf`,
   191  		},
   192  		{
   193  			desc: "float32 negative zero",
   194  			write: func(e *text.Encoder) {
   195  				e.WriteName("float32")
   196  				e.WriteFloat(math.Copysign(0, -1), 32)
   197  			},
   198  			wantOut:       `float32:-0`,
   199  			wantOutIndent: `float32: -0`,
   200  		},
   201  		{
   202  			desc: "int64 max value",
   203  			write: func(e *text.Encoder) {
   204  				e.WriteName("int")
   205  				e.WriteInt(math.MaxInt64)
   206  			},
   207  			wantOut:       `int:9223372036854775807`,
   208  			wantOutIndent: `int: 9223372036854775807`,
   209  		},
   210  		{
   211  			desc: "int64 min value",
   212  			write: func(e *text.Encoder) {
   213  				e.WriteName("int")
   214  				e.WriteInt(math.MinInt64)
   215  			},
   216  			wantOut:       `int:-9223372036854775808`,
   217  			wantOutIndent: `int: -9223372036854775808`,
   218  		},
   219  		{
   220  			desc: "uint",
   221  			write: func(e *text.Encoder) {
   222  				e.WriteName("uint")
   223  				e.WriteUint(math.MaxUint64)
   224  			},
   225  			wantOut:       `uint:18446744073709551615`,
   226  			wantOutIndent: `uint: 18446744073709551615`,
   227  		},
   228  		{
   229  			desc: "empty message field",
   230  			write: func(e *text.Encoder) {
   231  				e.WriteName("m")
   232  				e.StartMessage()
   233  				e.EndMessage()
   234  			},
   235  			wantOut:       `m:{}`,
   236  			wantOutIndent: `m: {}`,
   237  		},
   238  		{
   239  			desc: "multiple fields",
   240  			write: func(e *text.Encoder) {
   241  				e.WriteName("bool")
   242  				e.WriteBool(true)
   243  				e.WriteName("str")
   244  				e.WriteString("hello")
   245  				e.WriteName("str")
   246  				e.WriteString("world")
   247  				e.WriteName("m")
   248  				e.StartMessage()
   249  				e.EndMessage()
   250  				e.WriteName("[int]")
   251  				e.WriteInt(49)
   252  				e.WriteName("float64")
   253  				e.WriteFloat(1.00023e4, 64)
   254  				e.WriteName("101")
   255  				e.WriteString("unknown")
   256  			},
   257  			wantOut: `bool:true str:"hello" str:"world" m:{} [int]:49 float64:10002.3 101:"unknown"`,
   258  			wantOutIndent: `bool: true
   259  str: "hello"
   260  str: "world"
   261  m: {}
   262  [int]: 49
   263  float64: 10002.3
   264  101: "unknown"`,
   265  		},
   266  		{
   267  			desc: "populated message fields",
   268  			write: func(e *text.Encoder) {
   269  				e.WriteName("m1")
   270  				e.StartMessage()
   271  				{
   272  					e.WriteName("str")
   273  					e.WriteString("hello")
   274  				}
   275  				e.EndMessage()
   276  
   277  				e.WriteName("bool")
   278  				e.WriteBool(true)
   279  
   280  				e.WriteName("m2")
   281  				e.StartMessage()
   282  				{
   283  					e.WriteName("str")
   284  					e.WriteString("world")
   285  					e.WriteName("m2-1")
   286  					e.StartMessage()
   287  					e.EndMessage()
   288  					e.WriteName("m2-2")
   289  					e.StartMessage()
   290  					{
   291  						e.WriteName("[int]")
   292  						e.WriteInt(49)
   293  					}
   294  					e.EndMessage()
   295  					e.WriteName("float64")
   296  					e.WriteFloat(1.00023e4, 64)
   297  				}
   298  				e.EndMessage()
   299  
   300  				e.WriteName("101")
   301  				e.WriteString("unknown")
   302  			},
   303  			wantOut: `m1:{str:"hello"} bool:true m2:{str:"world" m2-1:{} m2-2:{[int]:49} float64:10002.3} 101:"unknown"`,
   304  			wantOutIndent: `m1: {
   305  	str: "hello"
   306  }
   307  bool: true
   308  m2: {
   309  	str: "world"
   310  	m2-1: {}
   311  	m2-2: {
   312  		[int]: 49
   313  	}
   314  	float64: 10002.3
   315  }
   316  101: "unknown"`,
   317  		},
   318  	}
   319  
   320  	for _, tc := range tests {
   321  		t.Run(tc.desc, func(t *testing.T) {
   322  			runEncoderTest(t, tc, [2]byte{})
   323  
   324  			// Test using the angle brackets.
   325  			// Testcases should not contain characters '{' and '}'.
   326  			tc.wantOut = replaceDelims(tc.wantOut)
   327  			tc.wantOutIndent = replaceDelims(tc.wantOutIndent)
   328  			runEncoderTest(t, tc, [2]byte{'<', '>'})
   329  		})
   330  	}
   331  }
   332  
   333  type encoderTestCase struct {
   334  	desc          string
   335  	write         func(*text.Encoder)
   336  	wantOut       string
   337  	wantOutIndent string
   338  }
   339  
   340  func runEncoderTest(t *testing.T, tc encoderTestCase, delims [2]byte) {
   341  	t.Helper()
   342  
   343  	if tc.wantOut != "" {
   344  		enc, err := text.NewEncoder(nil, "", delims, false)
   345  		if err != nil {
   346  			t.Fatalf("NewEncoder returned error: %v", err)
   347  		}
   348  		tc.write(enc)
   349  		got := string(enc.Bytes())
   350  		if got != tc.wantOut {
   351  			t.Errorf("(compact)\n<got>\n%v\n<want>\n%v\n", got, tc.wantOut)
   352  		}
   353  	}
   354  	if tc.wantOutIndent != "" {
   355  		enc, err := text.NewEncoder(nil, "\t", delims, false)
   356  		if err != nil {
   357  			t.Fatalf("NewEncoder returned error: %v", err)
   358  		}
   359  		tc.write(enc)
   360  		got, want := string(enc.Bytes()), tc.wantOutIndent
   361  		if got != want {
   362  			t.Errorf("(multi-line)\n<got>\n%v\n<want>\n%v\n<diff -want +got>\n%v\n",
   363  				got, want, cmp.Diff(want, got))
   364  		}
   365  	}
   366  }
   367  
   368  func replaceDelims(s string) string {
   369  	s = strings.Replace(s, "{", "<", -1)
   370  	return strings.Replace(s, "}", ">", -1)
   371  }
   372  
   373  // Test for UTF-8 and ASCII outputs.
   374  func TestEncodeStrings(t *testing.T) {
   375  	tests := []struct {
   376  		in           string
   377  		wantOut      string
   378  		wantOutASCII string
   379  	}{
   380  		{
   381  			in:      `"`,
   382  			wantOut: `"\""`,
   383  		},
   384  		{
   385  			in:      `'`,
   386  			wantOut: `"'"`,
   387  		},
   388  		{
   389  			in:           "hello\u1234world",
   390  			wantOut:      "\"hello\u1234world\"",
   391  			wantOutASCII: `"hello\u1234world"`,
   392  		},
   393  		{
   394  			// String that has as few escaped characters as possible.
   395  			in: func() string {
   396  				var b []byte
   397  				for i := rune(0); i <= 0x00a0; i++ {
   398  					switch i {
   399  					case 0, '\\', '\n', '\'': // these must be escaped, so ignore them
   400  					default:
   401  						var r [utf8.UTFMax]byte
   402  						n := utf8.EncodeRune(r[:], i)
   403  						b = append(b, r[:n]...)
   404  					}
   405  				}
   406  				return string(b)
   407  			}(),
   408  			wantOut:      `"\x01\x02\x03\x04\x05\x06\x07\x08\t\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_` + "`" + `abcdefghijklmnopqrstuvwxyz{|}~\x7f\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008a\u008b\u008c\u008d\u008e\u008f\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009a\u009b\u009c\u009d\u009e\u009f` + "\u00a0" + `"`,
   409  			wantOutASCII: `"\x01\x02\x03\x04\x05\x06\x07\x08\t\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_` + "`" + `abcdefghijklmnopqrstuvwxyz{|}~\x7f\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008a\u008b\u008c\u008d\u008e\u008f\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009a\u009b\u009c\u009d\u009e\u009f\u00a0"`,
   410  		},
   411  		{
   412  			// Valid UTF-8 wire encoding of the RuneError rune.
   413  			in:           string(utf8.RuneError),
   414  			wantOut:      `"` + string(utf8.RuneError) + `"`,
   415  			wantOutASCII: `"\ufffd"`,
   416  		},
   417  		{
   418  			in:           "\"'\\?\a\b\n\r\t\v\f\x01\nS\n\xab\x12\uab8f\U0010ffff",
   419  			wantOut:      `"\"'\\?\x07\x08\n\r\t\x0b\x0c\x01\nS\n\xab\x12` + "\uab8f\U0010ffff" + `"`,
   420  			wantOutASCII: `"\"'\\?\x07\x08\n\r\t\x0b\x0c\x01\nS\n\xab\x12\uab8f\U0010ffff"`,
   421  		},
   422  		{
   423  			in:           "\001x",
   424  			wantOut:      `"\x01x"`,
   425  			wantOutASCII: `"\x01x"`,
   426  		},
   427  		{
   428  			in:           "\012x",
   429  			wantOut:      `"\nx"`,
   430  			wantOutASCII: `"\nx"`,
   431  		},
   432  		{
   433  			in:           "\123x",
   434  			wantOut:      `"Sx"`,
   435  			wantOutASCII: `"Sx"`,
   436  		},
   437  		{
   438  			in:           "\1234x",
   439  			wantOut:      `"S4x"`,
   440  			wantOutASCII: `"S4x"`,
   441  		},
   442  		{
   443  			in:           "\001",
   444  			wantOut:      `"\x01"`,
   445  			wantOutASCII: `"\x01"`,
   446  		},
   447  		{
   448  			in:           "\012",
   449  			wantOut:      `"\n"`,
   450  			wantOutASCII: `"\n"`,
   451  		},
   452  		{
   453  			in:           "\123",
   454  			wantOut:      `"S"`,
   455  			wantOutASCII: `"S"`,
   456  		},
   457  		{
   458  			in:           "\1234",
   459  			wantOut:      `"S4"`,
   460  			wantOutASCII: `"S4"`,
   461  		},
   462  		{
   463  			in:           "\377",
   464  			wantOut:      `"\xff"`,
   465  			wantOutASCII: `"\xff"`,
   466  		},
   467  		{
   468  			in:           "\x0fx",
   469  			wantOut:      `"\x0fx"`,
   470  			wantOutASCII: `"\x0fx"`,
   471  		},
   472  		{
   473  			in:           "\xffx",
   474  			wantOut:      `"\xffx"`,
   475  			wantOutASCII: `"\xffx"`,
   476  		},
   477  		{
   478  			in:           "\xfffx",
   479  			wantOut:      `"\xfffx"`,
   480  			wantOutASCII: `"\xfffx"`,
   481  		},
   482  		{
   483  			in:           "\x0f",
   484  			wantOut:      `"\x0f"`,
   485  			wantOutASCII: `"\x0f"`,
   486  		},
   487  		{
   488  			in:           "\x7f",
   489  			wantOut:      `"\x7f"`,
   490  			wantOutASCII: `"\x7f"`,
   491  		},
   492  		{
   493  			in:           "\xff",
   494  			wantOut:      `"\xff"`,
   495  			wantOutASCII: `"\xff"`,
   496  		},
   497  		{
   498  			in:           "\xfff",
   499  			wantOut:      `"\xfff"`,
   500  			wantOutASCII: `"\xfff"`,
   501  		},
   502  	}
   503  	for _, tc := range tests {
   504  		t.Run("", func(t *testing.T) {
   505  			if tc.wantOut != "" {
   506  				runEncodeStringsTest(t, tc.in, tc.wantOut, false)
   507  			}
   508  			if tc.wantOutASCII != "" {
   509  				runEncodeStringsTest(t, tc.in, tc.wantOutASCII, true)
   510  			}
   511  		})
   512  	}
   513  }
   514  
   515  func runEncodeStringsTest(t *testing.T, in string, want string, outputASCII bool) {
   516  	t.Helper()
   517  
   518  	charType := "UTF-8"
   519  	if outputASCII {
   520  		charType = "ASCII"
   521  	}
   522  
   523  	enc, err := text.NewEncoder(nil, "", [2]byte{}, outputASCII)
   524  	if err != nil {
   525  		t.Fatalf("[%s] NewEncoder returned error: %v", charType, err)
   526  	}
   527  	enc.WriteString(in)
   528  	got := string(enc.Bytes())
   529  	if got != want {
   530  		t.Errorf("[%s] WriteString(%q)\n<got>\n%v\n<want>\n%v\n", charType, in, got, want)
   531  	}
   532  }
   533  
   534  func TestReset(t *testing.T) {
   535  	enc, err := text.NewEncoder(nil, "\t", [2]byte{}, false)
   536  	if err != nil {
   537  		t.Fatalf("NewEncoder returned error: %v", err)
   538  	}
   539  
   540  	enc.WriteName("foo")
   541  	pos := enc.Snapshot()
   542  
   543  	// Attempt to write a message value.
   544  	enc.StartMessage()
   545  	enc.WriteName("bar")
   546  	enc.WriteUint(10)
   547  
   548  	// Reset the value and decided to write a string value instead.
   549  	enc.Reset(pos)
   550  	enc.WriteString("0123456789")
   551  
   552  	got := string(enc.Bytes())
   553  	want := `foo: "0123456789"`
   554  	if got != want {
   555  		t.Errorf("Reset did not restore given position:\n<got>\n%v\n<want>\n%v\n", got, want)
   556  	}
   557  }
   558  

View as plain text