|  | // Copyright 2019 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package text_test | 
|  |  | 
|  | import ( | 
|  | "math" | 
|  | "strings" | 
|  | "testing" | 
|  | "unicode/utf8" | 
|  |  | 
|  | "github.com/google/go-cmp/cmp" | 
|  |  | 
|  | "google.golang.org/protobuf/internal/detrand" | 
|  | "google.golang.org/protobuf/internal/encoding/text" | 
|  | ) | 
|  |  | 
|  | // Disable detrand to enable direct comparisons on outputs. | 
|  | func init() { detrand.Disable() } | 
|  |  | 
|  | func TestEncoder(t *testing.T) { | 
|  | tests := []encoderTestCase{ | 
|  | { | 
|  | desc:          "no-opt", | 
|  | write:         func(e *text.Encoder) {}, | 
|  | wantOut:       ``, | 
|  | wantOutIndent: ``, | 
|  | }, | 
|  | { | 
|  | desc: "true", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("bool") | 
|  | e.WriteBool(true) | 
|  | }, | 
|  | wantOut:       `bool:true`, | 
|  | wantOutIndent: `bool: true`, | 
|  | }, | 
|  | { | 
|  | desc: "false", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("bool") | 
|  | e.WriteBool(false) | 
|  | }, | 
|  | wantOut:       `bool:false`, | 
|  | wantOutIndent: `bool: false`, | 
|  | }, | 
|  | { | 
|  | desc: "bracket name", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("[extension]") | 
|  | e.WriteString("hello") | 
|  | }, | 
|  | wantOut:       `[extension]:"hello"`, | 
|  | wantOutIndent: `[extension]: "hello"`, | 
|  | }, | 
|  | { | 
|  | desc: "numeric name", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("01234") | 
|  | e.WriteString("hello") | 
|  | }, | 
|  | wantOut:       `01234:"hello"`, | 
|  | wantOutIndent: `01234: "hello"`, | 
|  | }, | 
|  | { | 
|  | desc: "string", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("str") | 
|  | e.WriteString("hello world") | 
|  | }, | 
|  | wantOut:       `str:"hello world"`, | 
|  | wantOutIndent: `str: "hello world"`, | 
|  | }, | 
|  | { | 
|  | desc: "enum", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("enum") | 
|  | e.WriteLiteral("ENUM_VALUE") | 
|  | }, | 
|  | wantOut:       `enum:ENUM_VALUE`, | 
|  | wantOutIndent: `enum: ENUM_VALUE`, | 
|  | }, | 
|  | { | 
|  | desc: "float64", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float64") | 
|  | e.WriteFloat(1.0199999809265137, 64) | 
|  | }, | 
|  | wantOut:       `float64:1.0199999809265137`, | 
|  | wantOutIndent: `float64: 1.0199999809265137`, | 
|  | }, | 
|  | { | 
|  | desc: "float64 max value", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float64") | 
|  | e.WriteFloat(math.MaxFloat64, 64) | 
|  | }, | 
|  | wantOut:       `float64:1.7976931348623157e+308`, | 
|  | wantOutIndent: `float64: 1.7976931348623157e+308`, | 
|  | }, | 
|  | { | 
|  | desc: "float64 min value", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float64") | 
|  | e.WriteFloat(-math.MaxFloat64, 64) | 
|  | }, | 
|  | wantOut:       `float64:-1.7976931348623157e+308`, | 
|  | wantOutIndent: `float64: -1.7976931348623157e+308`, | 
|  | }, | 
|  | { | 
|  | desc: "float64 nan", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float64") | 
|  | e.WriteFloat(math.NaN(), 64) | 
|  | }, | 
|  | wantOut:       `float64:nan`, | 
|  | wantOutIndent: `float64: nan`, | 
|  | }, | 
|  | { | 
|  | desc: "float64 inf", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float64") | 
|  | e.WriteFloat(math.Inf(+1), 64) | 
|  | }, | 
|  | wantOut:       `float64:inf`, | 
|  | wantOutIndent: `float64: inf`, | 
|  | }, | 
|  | { | 
|  | desc: "float64 -inf", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float64") | 
|  | e.WriteFloat(math.Inf(-1), 64) | 
|  | }, | 
|  | wantOut:       `float64:-inf`, | 
|  | wantOutIndent: `float64: -inf`, | 
|  | }, | 
|  | { | 
|  | desc: "float64 negative zero", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float64") | 
|  | e.WriteFloat(math.Copysign(0, -1), 64) | 
|  | }, | 
|  | wantOut:       `float64:-0`, | 
|  | wantOutIndent: `float64: -0`, | 
|  | }, | 
|  | { | 
|  | desc: "float32", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float") | 
|  | e.WriteFloat(1.02, 32) | 
|  | }, | 
|  | wantOut:       `float:1.02`, | 
|  | wantOutIndent: `float: 1.02`, | 
|  | }, | 
|  | { | 
|  | desc: "float32 max value", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float32") | 
|  | e.WriteFloat(math.MaxFloat32, 32) | 
|  | }, | 
|  | wantOut:       `float32:3.4028235e+38`, | 
|  | wantOutIndent: `float32: 3.4028235e+38`, | 
|  | }, | 
|  | { | 
|  | desc: "float32 nan", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float32") | 
|  | e.WriteFloat(math.NaN(), 32) | 
|  | }, | 
|  | wantOut:       `float32:nan`, | 
|  | wantOutIndent: `float32: nan`, | 
|  | }, | 
|  | { | 
|  | desc: "float32 inf", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float32") | 
|  | e.WriteFloat(math.Inf(+1), 32) | 
|  | }, | 
|  | wantOut:       `float32:inf`, | 
|  | wantOutIndent: `float32: inf`, | 
|  | }, | 
|  | { | 
|  | desc: "float32 -inf", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float32") | 
|  | e.WriteFloat(math.Inf(-1), 32) | 
|  | }, | 
|  | wantOut:       `float32:-inf`, | 
|  | wantOutIndent: `float32: -inf`, | 
|  | }, | 
|  | { | 
|  | desc: "float32 negative zero", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("float32") | 
|  | e.WriteFloat(math.Copysign(0, -1), 32) | 
|  | }, | 
|  | wantOut:       `float32:-0`, | 
|  | wantOutIndent: `float32: -0`, | 
|  | }, | 
|  | { | 
|  | desc: "int64 max value", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("int") | 
|  | e.WriteInt(math.MaxInt64) | 
|  | }, | 
|  | wantOut:       `int:9223372036854775807`, | 
|  | wantOutIndent: `int: 9223372036854775807`, | 
|  | }, | 
|  | { | 
|  | desc: "int64 min value", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("int") | 
|  | e.WriteInt(math.MinInt64) | 
|  | }, | 
|  | wantOut:       `int:-9223372036854775808`, | 
|  | wantOutIndent: `int: -9223372036854775808`, | 
|  | }, | 
|  | { | 
|  | desc: "uint", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("uint") | 
|  | e.WriteUint(math.MaxUint64) | 
|  | }, | 
|  | wantOut:       `uint:18446744073709551615`, | 
|  | wantOutIndent: `uint: 18446744073709551615`, | 
|  | }, | 
|  | { | 
|  | desc: "empty message field", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("m") | 
|  | e.StartMessage() | 
|  | e.EndMessage() | 
|  | }, | 
|  | wantOut:       `m:{}`, | 
|  | wantOutIndent: `m: {}`, | 
|  | }, | 
|  | { | 
|  | desc: "multiple fields", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("bool") | 
|  | e.WriteBool(true) | 
|  | e.WriteName("str") | 
|  | e.WriteString("hello") | 
|  | e.WriteName("str") | 
|  | e.WriteString("world") | 
|  | e.WriteName("m") | 
|  | e.StartMessage() | 
|  | e.EndMessage() | 
|  | e.WriteName("[int]") | 
|  | e.WriteInt(49) | 
|  | e.WriteName("float64") | 
|  | e.WriteFloat(1.00023e4, 64) | 
|  | e.WriteName("101") | 
|  | e.WriteString("unknown") | 
|  | }, | 
|  | wantOut: `bool:true str:"hello" str:"world" m:{} [int]:49 float64:10002.3 101:"unknown"`, | 
|  | wantOutIndent: `bool: true | 
|  | str: "hello" | 
|  | str: "world" | 
|  | m: {} | 
|  | [int]: 49 | 
|  | float64: 10002.3 | 
|  | 101: "unknown"`, | 
|  | }, | 
|  | { | 
|  | desc: "populated message fields", | 
|  | write: func(e *text.Encoder) { | 
|  | e.WriteName("m1") | 
|  | e.StartMessage() | 
|  | { | 
|  | e.WriteName("str") | 
|  | e.WriteString("hello") | 
|  | } | 
|  | e.EndMessage() | 
|  |  | 
|  | e.WriteName("bool") | 
|  | e.WriteBool(true) | 
|  |  | 
|  | e.WriteName("m2") | 
|  | e.StartMessage() | 
|  | { | 
|  | e.WriteName("str") | 
|  | e.WriteString("world") | 
|  | e.WriteName("m2-1") | 
|  | e.StartMessage() | 
|  | e.EndMessage() | 
|  | e.WriteName("m2-2") | 
|  | e.StartMessage() | 
|  | { | 
|  | e.WriteName("[int]") | 
|  | e.WriteInt(49) | 
|  | } | 
|  | e.EndMessage() | 
|  | e.WriteName("float64") | 
|  | e.WriteFloat(1.00023e4, 64) | 
|  | } | 
|  | e.EndMessage() | 
|  |  | 
|  | e.WriteName("101") | 
|  | e.WriteString("unknown") | 
|  | }, | 
|  | wantOut: `m1:{str:"hello"} bool:true m2:{str:"world" m2-1:{} m2-2:{[int]:49} float64:10002.3} 101:"unknown"`, | 
|  | wantOutIndent: `m1: { | 
|  | str: "hello" | 
|  | } | 
|  | bool: true | 
|  | m2: { | 
|  | str: "world" | 
|  | m2-1: {} | 
|  | m2-2: { | 
|  | [int]: 49 | 
|  | } | 
|  | float64: 10002.3 | 
|  | } | 
|  | 101: "unknown"`, | 
|  | }, | 
|  | } | 
|  |  | 
|  | for _, tc := range tests { | 
|  | t.Run(tc.desc, func(t *testing.T) { | 
|  | runEncoderTest(t, tc, [2]byte{}) | 
|  |  | 
|  | // Test using the angle brackets. | 
|  | // Testcases should not contain characters '{' and '}'. | 
|  | tc.wantOut = replaceDelims(tc.wantOut) | 
|  | tc.wantOutIndent = replaceDelims(tc.wantOutIndent) | 
|  | runEncoderTest(t, tc, [2]byte{'<', '>'}) | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | type encoderTestCase struct { | 
|  | desc          string | 
|  | write         func(*text.Encoder) | 
|  | wantOut       string | 
|  | wantOutIndent string | 
|  | } | 
|  |  | 
|  | func runEncoderTest(t *testing.T, tc encoderTestCase, delims [2]byte) { | 
|  | t.Helper() | 
|  |  | 
|  | if tc.wantOut != "" { | 
|  | enc, err := text.NewEncoder(nil, "", delims, false) | 
|  | if err != nil { | 
|  | t.Fatalf("NewEncoder returned error: %v", err) | 
|  | } | 
|  | tc.write(enc) | 
|  | got := string(enc.Bytes()) | 
|  | if got != tc.wantOut { | 
|  | t.Errorf("(compact)\n<got>\n%v\n<want>\n%v\n", got, tc.wantOut) | 
|  | } | 
|  | } | 
|  | if tc.wantOutIndent != "" { | 
|  | enc, err := text.NewEncoder(nil, "\t", delims, false) | 
|  | if err != nil { | 
|  | t.Fatalf("NewEncoder returned error: %v", err) | 
|  | } | 
|  | tc.write(enc) | 
|  | got, want := string(enc.Bytes()), tc.wantOutIndent | 
|  | if got != want { | 
|  | t.Errorf("(multi-line)\n<got>\n%v\n<want>\n%v\n<diff -want +got>\n%v\n", | 
|  | got, want, cmp.Diff(want, got)) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func replaceDelims(s string) string { | 
|  | s = strings.Replace(s, "{", "<", -1) | 
|  | return strings.Replace(s, "}", ">", -1) | 
|  | } | 
|  |  | 
|  | // Test for UTF-8 and ASCII outputs. | 
|  | func TestEncodeStrings(t *testing.T) { | 
|  | tests := []struct { | 
|  | in           string | 
|  | wantOut      string | 
|  | wantOutASCII string | 
|  | }{ | 
|  | { | 
|  | in:      `"`, | 
|  | wantOut: `"\""`, | 
|  | }, | 
|  | { | 
|  | in:      `'`, | 
|  | wantOut: `"'"`, | 
|  | }, | 
|  | { | 
|  | in:           "hello\u1234world", | 
|  | wantOut:      "\"hello\u1234world\"", | 
|  | wantOutASCII: `"hello\u1234world"`, | 
|  | }, | 
|  | { | 
|  | // String that has as few escaped characters as possible. | 
|  | in: func() string { | 
|  | var b []byte | 
|  | for i := rune(0); i <= 0x00a0; i++ { | 
|  | switch i { | 
|  | case 0, '\\', '\n', '\'': // these must be escaped, so ignore them | 
|  | default: | 
|  | var r [utf8.UTFMax]byte | 
|  | n := utf8.EncodeRune(r[:], i) | 
|  | b = append(b, r[:n]...) | 
|  | } | 
|  | } | 
|  | return string(b) | 
|  | }(), | 
|  | 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" + `"`, | 
|  | 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"`, | 
|  | }, | 
|  | { | 
|  | // Valid UTF-8 wire encoding of the RuneError rune. | 
|  | in:           string(utf8.RuneError), | 
|  | wantOut:      `"` + string(utf8.RuneError) + `"`, | 
|  | wantOutASCII: `"\ufffd"`, | 
|  | }, | 
|  | { | 
|  | in:           "\"'\\?\a\b\n\r\t\v\f\x01\nS\n\xab\x12\uab8f\U0010ffff", | 
|  | wantOut:      `"\"'\\?\x07\x08\n\r\t\x0b\x0c\x01\nS\n\xab\x12` + "\uab8f\U0010ffff" + `"`, | 
|  | wantOutASCII: `"\"'\\?\x07\x08\n\r\t\x0b\x0c\x01\nS\n\xab\x12\uab8f\U0010ffff"`, | 
|  | }, | 
|  | { | 
|  | in:           "\001x", | 
|  | wantOut:      `"\x01x"`, | 
|  | wantOutASCII: `"\x01x"`, | 
|  | }, | 
|  | { | 
|  | in:           "\012x", | 
|  | wantOut:      `"\nx"`, | 
|  | wantOutASCII: `"\nx"`, | 
|  | }, | 
|  | { | 
|  | in:           "\123x", | 
|  | wantOut:      `"Sx"`, | 
|  | wantOutASCII: `"Sx"`, | 
|  | }, | 
|  | { | 
|  | in:           "\1234x", | 
|  | wantOut:      `"S4x"`, | 
|  | wantOutASCII: `"S4x"`, | 
|  | }, | 
|  | { | 
|  | in:           "\001", | 
|  | wantOut:      `"\x01"`, | 
|  | wantOutASCII: `"\x01"`, | 
|  | }, | 
|  | { | 
|  | in:           "\012", | 
|  | wantOut:      `"\n"`, | 
|  | wantOutASCII: `"\n"`, | 
|  | }, | 
|  | { | 
|  | in:           "\123", | 
|  | wantOut:      `"S"`, | 
|  | wantOutASCII: `"S"`, | 
|  | }, | 
|  | { | 
|  | in:           "\1234", | 
|  | wantOut:      `"S4"`, | 
|  | wantOutASCII: `"S4"`, | 
|  | }, | 
|  | { | 
|  | in:           "\377", | 
|  | wantOut:      `"\xff"`, | 
|  | wantOutASCII: `"\xff"`, | 
|  | }, | 
|  | { | 
|  | in:           "\x0fx", | 
|  | wantOut:      `"\x0fx"`, | 
|  | wantOutASCII: `"\x0fx"`, | 
|  | }, | 
|  | { | 
|  | in:           "\xffx", | 
|  | wantOut:      `"\xffx"`, | 
|  | wantOutASCII: `"\xffx"`, | 
|  | }, | 
|  | { | 
|  | in:           "\xfffx", | 
|  | wantOut:      `"\xfffx"`, | 
|  | wantOutASCII: `"\xfffx"`, | 
|  | }, | 
|  | { | 
|  | in:           "\x0f", | 
|  | wantOut:      `"\x0f"`, | 
|  | wantOutASCII: `"\x0f"`, | 
|  | }, | 
|  | { | 
|  | in:           "\x7f", | 
|  | wantOut:      `"\x7f"`, | 
|  | wantOutASCII: `"\x7f"`, | 
|  | }, | 
|  | { | 
|  | in:           "\xff", | 
|  | wantOut:      `"\xff"`, | 
|  | wantOutASCII: `"\xff"`, | 
|  | }, | 
|  | { | 
|  | in:           "\xfff", | 
|  | wantOut:      `"\xfff"`, | 
|  | wantOutASCII: `"\xfff"`, | 
|  | }, | 
|  | } | 
|  | for _, tc := range tests { | 
|  | t.Run("", func(t *testing.T) { | 
|  | if tc.wantOut != "" { | 
|  | runEncodeStringsTest(t, tc.in, tc.wantOut, false) | 
|  | } | 
|  | if tc.wantOutASCII != "" { | 
|  | runEncodeStringsTest(t, tc.in, tc.wantOutASCII, true) | 
|  | } | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | func runEncodeStringsTest(t *testing.T, in string, want string, outputASCII bool) { | 
|  | t.Helper() | 
|  |  | 
|  | charType := "UTF-8" | 
|  | if outputASCII { | 
|  | charType = "ASCII" | 
|  | } | 
|  |  | 
|  | enc, err := text.NewEncoder(nil, "", [2]byte{}, outputASCII) | 
|  | if err != nil { | 
|  | t.Fatalf("[%s] NewEncoder returned error: %v", charType, err) | 
|  | } | 
|  | enc.WriteString(in) | 
|  | got := string(enc.Bytes()) | 
|  | if got != want { | 
|  | t.Errorf("[%s] WriteString(%q)\n<got>\n%v\n<want>\n%v\n", charType, in, got, want) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestReset(t *testing.T) { | 
|  | enc, err := text.NewEncoder(nil, "\t", [2]byte{}, false) | 
|  | if err != nil { | 
|  | t.Fatalf("NewEncoder returned error: %v", err) | 
|  | } | 
|  |  | 
|  | enc.WriteName("foo") | 
|  | pos := enc.Snapshot() | 
|  |  | 
|  | // Attempt to write a message value. | 
|  | enc.StartMessage() | 
|  | enc.WriteName("bar") | 
|  | enc.WriteUint(10) | 
|  |  | 
|  | // Reset the value and decided to write a string value instead. | 
|  | enc.Reset(pos) | 
|  | enc.WriteString("0123456789") | 
|  |  | 
|  | got := string(enc.Bytes()) | 
|  | want := `foo: "0123456789"` | 
|  | if got != want { | 
|  | t.Errorf("Reset did not restore given position:\n<got>\n%v\n<want>\n%v\n", got, want) | 
|  | } | 
|  | } |