| // 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("", 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("\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 := 0; i < utf8.RuneSelf; i++ { |
| switch i { |
| case 0, '\\', '\n', '\'': // these must be escaped, so ignore them |
| default: |
| b = append(b, byte(i)) |
| } |
| } |
| 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\"", |
| 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\"", |
| }, |
| { |
| // 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: "\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("", [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("\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) |
| } |
| } |