xref: /aosp_15_r20/external/golang-protobuf/internal/encoding/text/encode_test.go (revision 1c12ee1efe575feb122dbf939ff15148a3b3e8f2)
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
5package text_test
6
7import (
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.
20func init() { detrand.Disable() }
21
22func 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
259str: "hello"
260str: "world"
261m: {}
262[int]: 49
263float64: 10002.3
264101: "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}
307bool: true
308m2: {
309	str: "world"
310	m2-1: {}
311	m2-2: {
312		[int]: 49
313	}
314	float64: 10002.3
315}
316101: "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
333type encoderTestCase struct {
334	desc          string
335	write         func(*text.Encoder)
336	wantOut       string
337	wantOutIndent string
338}
339
340func runEncoderTest(t *testing.T, tc encoderTestCase, delims [2]byte) {
341	t.Helper()
342
343	if tc.wantOut != "" {
344		enc, err := text.NewEncoder("", 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("\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
368func 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.
374func 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
515func 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("", [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
534func TestReset(t *testing.T) {
535	enc, err := text.NewEncoder("\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