| // 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) | |
| } | |
| } |