xref: /aosp_15_r20/external/golang-protobuf/encoding/prototext/encode_test.go (revision 1c12ee1efe575feb122dbf939ff15148a3b3e8f2)
1// Copyright 2018 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 prototext_test
6
7import (
8	"math"
9	"testing"
10
11	"github.com/google/go-cmp/cmp"
12
13	"google.golang.org/protobuf/encoding/prototext"
14	"google.golang.org/protobuf/internal/detrand"
15	"google.golang.org/protobuf/internal/flags"
16	"google.golang.org/protobuf/proto"
17	"google.golang.org/protobuf/reflect/protoregistry"
18	"google.golang.org/protobuf/testing/protopack"
19
20	pb2 "google.golang.org/protobuf/internal/testprotos/textpb2"
21	pb3 "google.golang.org/protobuf/internal/testprotos/textpb3"
22	"google.golang.org/protobuf/types/known/anypb"
23)
24
25func init() {
26	// Disable detrand to enable direct comparisons on outputs.
27	detrand.Disable()
28}
29
30func TestMarshal(t *testing.T) {
31	tests := []struct {
32		desc    string
33		mo      prototext.MarshalOptions
34		input   proto.Message
35		want    string
36		wantErr bool // TODO: Verify error message content.
37		skip    bool
38	}{{
39		desc:  "proto2 optional scalars not set",
40		input: &pb2.Scalars{},
41		want:  "",
42	}, {
43		desc:  "proto3 scalars not set",
44		input: &pb3.Scalars{},
45		want:  "",
46	}, {
47		desc:  "proto3 optional not set",
48		input: &pb3.Proto3Optional{},
49		want:  "",
50	}, {
51		desc: "proto2 optional scalars set to zero values",
52		input: &pb2.Scalars{
53			OptBool:     proto.Bool(false),
54			OptInt32:    proto.Int32(0),
55			OptInt64:    proto.Int64(0),
56			OptUint32:   proto.Uint32(0),
57			OptUint64:   proto.Uint64(0),
58			OptSint32:   proto.Int32(0),
59			OptSint64:   proto.Int64(0),
60			OptFixed32:  proto.Uint32(0),
61			OptFixed64:  proto.Uint64(0),
62			OptSfixed32: proto.Int32(0),
63			OptSfixed64: proto.Int64(0),
64			OptFloat:    proto.Float32(0),
65			OptDouble:   proto.Float64(0),
66			OptBytes:    []byte{},
67			OptString:   proto.String(""),
68		},
69		want: `opt_bool: false
70opt_int32: 0
71opt_int64: 0
72opt_uint32: 0
73opt_uint64: 0
74opt_sint32: 0
75opt_sint64: 0
76opt_fixed32: 0
77opt_fixed64: 0
78opt_sfixed32: 0
79opt_sfixed64: 0
80opt_float: 0
81opt_double: 0
82opt_bytes: ""
83opt_string: ""
84`,
85	}, {
86		desc: "proto3 optional set to zero values",
87		input: &pb3.Proto3Optional{
88			OptBool:    proto.Bool(false),
89			OptInt32:   proto.Int32(0),
90			OptInt64:   proto.Int64(0),
91			OptUint32:  proto.Uint32(0),
92			OptUint64:  proto.Uint64(0),
93			OptFloat:   proto.Float32(0),
94			OptDouble:  proto.Float64(0),
95			OptString:  proto.String(""),
96			OptBytes:   []byte{},
97			OptEnum:    pb3.Enum_ZERO.Enum(),
98			OptMessage: &pb3.Nested{},
99		},
100		want: `opt_bool: false
101opt_int32: 0
102opt_int64: 0
103opt_uint32: 0
104opt_uint64: 0
105opt_float: 0
106opt_double: 0
107opt_string: ""
108opt_bytes: ""
109opt_enum: ZERO
110opt_message: {}
111`,
112	}, {
113		desc: "proto3 scalars set to zero values",
114		input: &pb3.Scalars{
115			SBool:     false,
116			SInt32:    0,
117			SInt64:    0,
118			SUint32:   0,
119			SUint64:   0,
120			SSint32:   0,
121			SSint64:   0,
122			SFixed32:  0,
123			SFixed64:  0,
124			SSfixed32: 0,
125			SSfixed64: 0,
126			SFloat:    0,
127			SDouble:   0,
128			SBytes:    []byte{},
129			SString:   "",
130		},
131		want: "",
132	}, {
133		desc: "proto2 optional scalars set to some values",
134		input: &pb2.Scalars{
135			OptBool:     proto.Bool(true),
136			OptInt32:    proto.Int32(0xff),
137			OptInt64:    proto.Int64(0xdeadbeef),
138			OptUint32:   proto.Uint32(47),
139			OptUint64:   proto.Uint64(0xdeadbeef),
140			OptSint32:   proto.Int32(-1001),
141			OptSint64:   proto.Int64(-0xffff),
142			OptFixed64:  proto.Uint64(64),
143			OptSfixed32: proto.Int32(-32),
144			OptFloat:    proto.Float32(1.02),
145			OptDouble:   proto.Float64(1.0199999809265137),
146			OptBytes:    []byte("\xe8\xb0\xb7\xe6\xad\x8c"),
147			OptString:   proto.String("谷歌"),
148		},
149		want: `opt_bool: true
150opt_int32: 255
151opt_int64: 3735928559
152opt_uint32: 47
153opt_uint64: 3735928559
154opt_sint32: -1001
155opt_sint64: -65535
156opt_fixed64: 64
157opt_sfixed32: -32
158opt_float: 1.02
159opt_double: 1.0199999809265137
160opt_bytes: "谷歌"
161opt_string: "谷歌"
162`,
163	}, {
164		desc: "proto2 string with invalid UTF-8",
165		input: &pb2.Scalars{
166			OptString: proto.String("abc\xff"),
167		},
168		want: `opt_string: "abc\xff"
169`,
170	}, {
171		desc: "proto3 string with invalid UTF-8",
172		input: &pb3.Scalars{
173			SString: "abc\xff",
174		},
175		wantErr: true,
176	}, {
177		desc: "float nan",
178		input: &pb3.Scalars{
179			SFloat: float32(math.NaN()),
180		},
181		want: "s_float: nan\n",
182	}, {
183		desc: "float positive infinity",
184		input: &pb3.Scalars{
185			SFloat: float32(math.Inf(1)),
186		},
187		want: "s_float: inf\n",
188	}, {
189		desc: "float negative infinity",
190		input: &pb3.Scalars{
191			SFloat: float32(math.Inf(-1)),
192		},
193		want: "s_float: -inf\n",
194	}, {
195		desc: "double nan",
196		input: &pb3.Scalars{
197			SDouble: math.NaN(),
198		},
199		want: "s_double: nan\n",
200	}, {
201		desc: "double positive infinity",
202		input: &pb3.Scalars{
203			SDouble: math.Inf(1),
204		},
205		want: "s_double: inf\n",
206	}, {
207		desc: "double negative infinity",
208		input: &pb3.Scalars{
209			SDouble: math.Inf(-1),
210		},
211		want: "s_double: -inf\n",
212	}, {
213		desc:  "proto2 enum not set",
214		input: &pb2.Enums{},
215		want:  "",
216	}, {
217		desc: "proto2 enum set to zero value",
218		input: &pb2.Enums{
219			OptEnum:       pb2.Enum(0).Enum(),
220			OptNestedEnum: pb2.Enums_NestedEnum(0).Enum(),
221		},
222		want: `opt_enum: 0
223opt_nested_enum: 0
224`,
225	}, {
226		desc: "proto2 enum",
227		input: &pb2.Enums{
228			OptEnum:       pb2.Enum_ONE.Enum(),
229			OptNestedEnum: pb2.Enums_UNO.Enum(),
230		},
231		want: `opt_enum: ONE
232opt_nested_enum: UNO
233`,
234	}, {
235		desc: "proto2 enum set to numeric values",
236		input: &pb2.Enums{
237			OptEnum:       pb2.Enum(2).Enum(),
238			OptNestedEnum: pb2.Enums_NestedEnum(2).Enum(),
239		},
240		want: `opt_enum: TWO
241opt_nested_enum: DOS
242`,
243	}, {
244		desc: "proto2 enum set to unnamed numeric values",
245		input: &pb2.Enums{
246			OptEnum:       pb2.Enum(101).Enum(),
247			OptNestedEnum: pb2.Enums_NestedEnum(-101).Enum(),
248		},
249		want: `opt_enum: 101
250opt_nested_enum: -101
251`,
252	}, {
253		desc:  "proto3 enum not set",
254		input: &pb3.Enums{},
255		want:  "",
256	}, {
257		desc: "proto3 enum set to zero value",
258		input: &pb3.Enums{
259			SEnum:       pb3.Enum_ZERO,
260			SNestedEnum: pb3.Enums_CERO,
261		},
262		want: "",
263	}, {
264		desc: "proto3 enum",
265		input: &pb3.Enums{
266			SEnum:       pb3.Enum_ONE,
267			SNestedEnum: pb3.Enums_UNO,
268		},
269		want: `s_enum: ONE
270s_nested_enum: UNO
271`,
272	}, {
273		desc: "proto3 enum set to numeric values",
274		input: &pb3.Enums{
275			SEnum:       2,
276			SNestedEnum: 2,
277		},
278		want: `s_enum: TWO
279s_nested_enum: DOS
280`,
281	}, {
282		desc: "proto3 enum set to unnamed numeric values",
283		input: &pb3.Enums{
284			SEnum:       -47,
285			SNestedEnum: 47,
286		},
287		want: `s_enum: -47
288s_nested_enum: 47
289`,
290	}, {
291		desc:  "proto2 nested message not set",
292		input: &pb2.Nests{},
293		want:  "",
294	}, {
295		desc: "proto2 nested message set to empty",
296		input: &pb2.Nests{
297			OptNested: &pb2.Nested{},
298			Optgroup:  &pb2.Nests_OptGroup{},
299		},
300		want: `opt_nested: {}
301OptGroup: {}
302`,
303	}, {
304		desc: "proto2 nested messages",
305		input: &pb2.Nests{
306			OptNested: &pb2.Nested{
307				OptString: proto.String("nested message"),
308				OptNested: &pb2.Nested{
309					OptString: proto.String("another nested message"),
310				},
311			},
312		},
313		want: `opt_nested: {
314  opt_string: "nested message"
315  opt_nested: {
316    opt_string: "another nested message"
317  }
318}
319`,
320	}, {
321		desc: "proto2 groups",
322		input: &pb2.Nests{
323			Optgroup: &pb2.Nests_OptGroup{
324				OptString: proto.String("inside a group"),
325				OptNested: &pb2.Nested{
326					OptString: proto.String("nested message inside a group"),
327				},
328				Optnestedgroup: &pb2.Nests_OptGroup_OptNestedGroup{
329					OptFixed32: proto.Uint32(47),
330				},
331			},
332		},
333		want: `OptGroup: {
334  opt_string: "inside a group"
335  opt_nested: {
336    opt_string: "nested message inside a group"
337  }
338  OptNestedGroup: {
339    opt_fixed32: 47
340  }
341}
342`,
343	}, {
344		desc:  "proto3 nested message not set",
345		input: &pb3.Nests{},
346		want:  "",
347	}, {
348		desc: "proto3 nested message set to empty",
349		input: &pb3.Nests{
350			SNested: &pb3.Nested{},
351		},
352		want: "s_nested: {}\n",
353	}, {
354		desc: "proto3 nested message",
355		input: &pb3.Nests{
356			SNested: &pb3.Nested{
357				SString: "nested message",
358				SNested: &pb3.Nested{
359					SString: "another nested message",
360				},
361			},
362		},
363		want: `s_nested: {
364  s_string: "nested message"
365  s_nested: {
366    s_string: "another nested message"
367  }
368}
369`,
370	}, {
371		desc: "proto3 nested message contains invalid UTF-8",
372		input: &pb3.Nests{
373			SNested: &pb3.Nested{
374				SString: "abc\xff",
375			},
376		},
377		wantErr: true,
378	}, {
379		desc:  "oneof not set",
380		input: &pb3.Oneofs{},
381		want:  "",
382	}, {
383		desc: "oneof set to empty string",
384		input: &pb3.Oneofs{
385			Union: &pb3.Oneofs_OneofString{},
386		},
387		want: `oneof_string: ""
388`,
389	}, {
390		desc: "oneof set to string",
391		input: &pb3.Oneofs{
392			Union: &pb3.Oneofs_OneofString{
393				OneofString: "hello",
394			},
395		},
396		want: `oneof_string: "hello"
397`,
398	}, {
399		desc: "oneof set to enum",
400		input: &pb3.Oneofs{
401			Union: &pb3.Oneofs_OneofEnum{
402				OneofEnum: pb3.Enum_ZERO,
403			},
404		},
405		want: `oneof_enum: ZERO
406`,
407	}, {
408		desc: "oneof set to empty message",
409		input: &pb3.Oneofs{
410			Union: &pb3.Oneofs_OneofNested{
411				OneofNested: &pb3.Nested{},
412			},
413		},
414		want: "oneof_nested: {}\n",
415	}, {
416		desc: "oneof set to message",
417		input: &pb3.Oneofs{
418			Union: &pb3.Oneofs_OneofNested{
419				OneofNested: &pb3.Nested{
420					SString: "nested message",
421				},
422			},
423		},
424		want: `oneof_nested: {
425  s_string: "nested message"
426}
427`,
428	}, {
429		desc:  "repeated fields not set",
430		input: &pb2.Repeats{},
431		want:  "",
432	}, {
433		desc: "repeated fields set to empty slices",
434		input: &pb2.Repeats{
435			RptBool:   []bool{},
436			RptInt32:  []int32{},
437			RptInt64:  []int64{},
438			RptUint32: []uint32{},
439			RptUint64: []uint64{},
440			RptFloat:  []float32{},
441			RptDouble: []float64{},
442			RptBytes:  [][]byte{},
443		},
444		want: "",
445	}, {
446		desc: "repeated fields set to some values",
447		input: &pb2.Repeats{
448			RptBool:   []bool{true, false, true, true},
449			RptInt32:  []int32{1, 6, 0, 0},
450			RptInt64:  []int64{-64, 47},
451			RptUint32: []uint32{0xff, 0xffff},
452			RptUint64: []uint64{0xdeadbeef},
453			RptFloat:  []float32{float32(math.NaN()), float32(math.Inf(1)), float32(math.Inf(-1)), 1.034},
454			RptDouble: []float64{math.NaN(), math.Inf(1), math.Inf(-1), 1.23e-308},
455			RptString: []string{"hello", "世界"},
456			RptBytes: [][]byte{
457				[]byte("hello"),
458				[]byte("\xe4\xb8\x96\xe7\x95\x8c"),
459			},
460		},
461		want: `rpt_bool: true
462rpt_bool: false
463rpt_bool: true
464rpt_bool: true
465rpt_int32: 1
466rpt_int32: 6
467rpt_int32: 0
468rpt_int32: 0
469rpt_int64: -64
470rpt_int64: 47
471rpt_uint32: 255
472rpt_uint32: 65535
473rpt_uint64: 3735928559
474rpt_float: nan
475rpt_float: inf
476rpt_float: -inf
477rpt_float: 1.034
478rpt_double: nan
479rpt_double: inf
480rpt_double: -inf
481rpt_double: 1.23e-308
482rpt_string: "hello"
483rpt_string: "世界"
484rpt_bytes: "hello"
485rpt_bytes: "世界"
486`,
487	}, {
488		desc: "repeated proto2 contains invalid UTF-8",
489		input: &pb2.Repeats{
490			RptString: []string{"abc\xff"},
491		},
492		want: `rpt_string: "abc\xff"
493`,
494	}, {
495		desc: "repeated proto3 contains invalid UTF-8",
496		input: &pb3.Repeats{
497			RptString: []string{"abc\xff"},
498		},
499		wantErr: true,
500	}, {
501		desc: "repeated enums",
502		input: &pb2.Enums{
503			RptEnum:       []pb2.Enum{pb2.Enum_ONE, 2, pb2.Enum_TEN, 42},
504			RptNestedEnum: []pb2.Enums_NestedEnum{2, 47, 10},
505		},
506		want: `rpt_enum: ONE
507rpt_enum: TWO
508rpt_enum: TEN
509rpt_enum: 42
510rpt_nested_enum: DOS
511rpt_nested_enum: 47
512rpt_nested_enum: DIEZ
513`,
514	}, {
515		desc: "repeated messages set to empty",
516		input: &pb2.Nests{
517			RptNested: []*pb2.Nested{},
518			Rptgroup:  []*pb2.Nests_RptGroup{},
519		},
520		want: "",
521	}, {
522		desc: "repeated messages",
523		input: &pb2.Nests{
524			RptNested: []*pb2.Nested{
525				{
526					OptString: proto.String("repeat nested one"),
527				},
528				{
529					OptString: proto.String("repeat nested two"),
530					OptNested: &pb2.Nested{
531						OptString: proto.String("inside repeat nested two"),
532					},
533				},
534				{},
535			},
536		},
537		want: `rpt_nested: {
538  opt_string: "repeat nested one"
539}
540rpt_nested: {
541  opt_string: "repeat nested two"
542  opt_nested: {
543    opt_string: "inside repeat nested two"
544  }
545}
546rpt_nested: {}
547`,
548	}, {
549		desc: "repeated messages contains nil value",
550		input: &pb2.Nests{
551			RptNested: []*pb2.Nested{nil, {}},
552		},
553		want: `rpt_nested: {}
554rpt_nested: {}
555`,
556	}, {
557		desc: "repeated groups",
558		input: &pb2.Nests{
559			Rptgroup: []*pb2.Nests_RptGroup{
560				{
561					RptString: []string{"hello", "world"},
562				},
563				{},
564				nil,
565			},
566		},
567		want: `RptGroup: {
568  rpt_string: "hello"
569  rpt_string: "world"
570}
571RptGroup: {}
572RptGroup: {}
573`,
574	}, {
575		desc:  "map fields not set",
576		input: &pb3.Maps{},
577		want:  "",
578	}, {
579		desc: "map fields set to empty",
580		input: &pb3.Maps{
581			Int32ToStr:   map[int32]string{},
582			BoolToUint32: map[bool]uint32{},
583			Uint64ToEnum: map[uint64]pb3.Enum{},
584			StrToNested:  map[string]*pb3.Nested{},
585			StrToOneofs:  map[string]*pb3.Oneofs{},
586		},
587		want: "",
588	}, {
589		desc: "map fields 1",
590		input: &pb3.Maps{
591			Int32ToStr: map[int32]string{
592				-101: "-101",
593				0xff: "0xff",
594				0:    "zero",
595			},
596			BoolToUint32: map[bool]uint32{
597				true:  42,
598				false: 101,
599			},
600		},
601		want: `int32_to_str: {
602  key: -101
603  value: "-101"
604}
605int32_to_str: {
606  key: 0
607  value: "zero"
608}
609int32_to_str: {
610  key: 255
611  value: "0xff"
612}
613bool_to_uint32: {
614  key: false
615  value: 101
616}
617bool_to_uint32: {
618  key: true
619  value: 42
620}
621`,
622	}, {
623		desc: "map fields 2",
624		input: &pb3.Maps{
625			Uint64ToEnum: map[uint64]pb3.Enum{
626				1:  pb3.Enum_ONE,
627				2:  pb3.Enum_TWO,
628				10: pb3.Enum_TEN,
629				47: 47,
630			},
631		},
632		want: `uint64_to_enum: {
633  key: 1
634  value: ONE
635}
636uint64_to_enum: {
637  key: 2
638  value: TWO
639}
640uint64_to_enum: {
641  key: 10
642  value: TEN
643}
644uint64_to_enum: {
645  key: 47
646  value: 47
647}
648`,
649	}, {
650		desc: "map fields 3",
651		input: &pb3.Maps{
652			StrToNested: map[string]*pb3.Nested{
653				"nested": &pb3.Nested{
654					SString: "nested in a map",
655				},
656			},
657		},
658		want: `str_to_nested: {
659  key: "nested"
660  value: {
661    s_string: "nested in a map"
662  }
663}
664`,
665	}, {
666		desc: "map fields 4",
667		input: &pb3.Maps{
668			StrToOneofs: map[string]*pb3.Oneofs{
669				"string": &pb3.Oneofs{
670					Union: &pb3.Oneofs_OneofString{
671						OneofString: "hello",
672					},
673				},
674				"nested": &pb3.Oneofs{
675					Union: &pb3.Oneofs_OneofNested{
676						OneofNested: &pb3.Nested{
677							SString: "nested oneof in map field value",
678						},
679					},
680				},
681			},
682		},
683		want: `str_to_oneofs: {
684  key: "nested"
685  value: {
686    oneof_nested: {
687      s_string: "nested oneof in map field value"
688    }
689  }
690}
691str_to_oneofs: {
692  key: "string"
693  value: {
694    oneof_string: "hello"
695  }
696}
697`,
698	}, {
699		desc: "proto2 map field value contains invalid UTF-8",
700		input: &pb2.Maps{
701			Int32ToStr: map[int32]string{
702				101: "abc\xff",
703			},
704		},
705		want: `int32_to_str: {
706  key: 101
707  value: "abc\xff"
708}
709`,
710	}, {
711		desc: "proto2 map field key contains invalid UTF-8",
712		input: &pb2.Maps{
713			StrToNested: map[string]*pb2.Nested{
714				"abc\xff": {},
715			},
716		},
717		want: `str_to_nested: {
718  key: "abc\xff"
719  value: {}
720}
721`,
722	}, {
723		desc: "proto3 map field value contains invalid UTF-8",
724		input: &pb3.Maps{
725			Int32ToStr: map[int32]string{
726				101: "abc\xff",
727			},
728		},
729		wantErr: true,
730	}, {
731		desc: "proto3 map field key contains invalid UTF-8",
732		input: &pb3.Maps{
733			StrToNested: map[string]*pb3.Nested{
734				"abc\xff": {},
735			},
736		},
737		wantErr: true,
738	}, {
739		desc: "map field contains nil value",
740		input: &pb3.Maps{
741			StrToNested: map[string]*pb3.Nested{
742				"nil": nil,
743			},
744		},
745		want: `str_to_nested: {
746  key: "nil"
747  value: {}
748}
749`,
750	}, {
751		desc:    "required fields not set",
752		input:   &pb2.Requireds{},
753		want:    "",
754		wantErr: true,
755	}, {
756		desc: "required fields partially set",
757		input: &pb2.Requireds{
758			ReqBool:     proto.Bool(false),
759			ReqSfixed64: proto.Int64(0xbeefcafe),
760			ReqDouble:   proto.Float64(math.NaN()),
761			ReqString:   proto.String("hello"),
762			ReqEnum:     pb2.Enum_ONE.Enum(),
763		},
764		want: `req_bool: false
765req_sfixed64: 3203386110
766req_double: nan
767req_string: "hello"
768req_enum: ONE
769`,
770		wantErr: true,
771	}, {
772		desc: "required fields not set with AllowPartial",
773		mo:   prototext.MarshalOptions{AllowPartial: true},
774		input: &pb2.Requireds{
775			ReqBool:     proto.Bool(false),
776			ReqSfixed64: proto.Int64(0xbeefcafe),
777			ReqDouble:   proto.Float64(math.NaN()),
778			ReqString:   proto.String("hello"),
779			ReqEnum:     pb2.Enum_ONE.Enum(),
780		},
781		want: `req_bool: false
782req_sfixed64: 3203386110
783req_double: nan
784req_string: "hello"
785req_enum: ONE
786`,
787	}, {
788		desc: "required fields all set",
789		input: &pb2.Requireds{
790			ReqBool:     proto.Bool(false),
791			ReqSfixed64: proto.Int64(0),
792			ReqDouble:   proto.Float64(1.23),
793			ReqString:   proto.String(""),
794			ReqEnum:     pb2.Enum_ONE.Enum(),
795			ReqNested:   &pb2.Nested{},
796		},
797		want: `req_bool: false
798req_sfixed64: 0
799req_double: 1.23
800req_string: ""
801req_enum: ONE
802req_nested: {}
803`,
804	}, {
805		desc: "indirect required field",
806		input: &pb2.IndirectRequired{
807			OptNested: &pb2.NestedWithRequired{},
808		},
809		want:    "opt_nested: {}\n",
810		wantErr: true,
811	}, {
812		desc: "indirect required field with AllowPartial",
813		mo:   prototext.MarshalOptions{AllowPartial: true},
814		input: &pb2.IndirectRequired{
815			OptNested: &pb2.NestedWithRequired{},
816		},
817		want: "opt_nested: {}\n",
818	}, {
819		desc: "indirect required field in empty repeated",
820		input: &pb2.IndirectRequired{
821			RptNested: []*pb2.NestedWithRequired{},
822		},
823		want: "",
824	}, {
825		desc: "indirect required field in repeated",
826		input: &pb2.IndirectRequired{
827			RptNested: []*pb2.NestedWithRequired{
828				&pb2.NestedWithRequired{},
829			},
830		},
831		want:    "rpt_nested: {}\n",
832		wantErr: true,
833	}, {
834		desc: "indirect required field in repeated with AllowPartial",
835		mo:   prototext.MarshalOptions{AllowPartial: true},
836		input: &pb2.IndirectRequired{
837			RptNested: []*pb2.NestedWithRequired{
838				&pb2.NestedWithRequired{},
839			},
840		},
841		want: "rpt_nested: {}\n",
842	}, {
843		desc: "indirect required field in empty map",
844		input: &pb2.IndirectRequired{
845			StrToNested: map[string]*pb2.NestedWithRequired{},
846		},
847		want: "",
848	}, {
849		desc: "indirect required field in map",
850		input: &pb2.IndirectRequired{
851			StrToNested: map[string]*pb2.NestedWithRequired{
852				"fail": &pb2.NestedWithRequired{},
853			},
854		},
855		want: `str_to_nested: {
856  key: "fail"
857  value: {}
858}
859`,
860		wantErr: true,
861	}, {
862		desc: "indirect required field in map with AllowPartial",
863		mo:   prototext.MarshalOptions{AllowPartial: true},
864		input: &pb2.IndirectRequired{
865			StrToNested: map[string]*pb2.NestedWithRequired{
866				"fail": &pb2.NestedWithRequired{},
867			},
868		},
869		want: `str_to_nested: {
870  key: "fail"
871  value: {}
872}
873`,
874	}, {
875		desc: "indirect required field in oneof",
876		input: &pb2.IndirectRequired{
877			Union: &pb2.IndirectRequired_OneofNested{
878				OneofNested: &pb2.NestedWithRequired{},
879			},
880		},
881		want:    "oneof_nested: {}\n",
882		wantErr: true,
883	}, {
884		desc: "indirect required field in oneof with AllowPartial",
885		mo:   prototext.MarshalOptions{AllowPartial: true},
886		input: &pb2.IndirectRequired{
887			Union: &pb2.IndirectRequired_OneofNested{
888				OneofNested: &pb2.NestedWithRequired{},
889			},
890		},
891		want: "oneof_nested: {}\n",
892	}, {
893		desc: "unknown fields not printed",
894		input: func() proto.Message {
895			m := &pb2.Scalars{
896				OptString: proto.String("this message contains unknown fields"),
897			}
898			m.ProtoReflect().SetUnknown(protopack.Message{
899				protopack.Tag{101, protopack.VarintType}, protopack.Bool(true),
900				protopack.Tag{102, protopack.VarintType}, protopack.Varint(0xff),
901				protopack.Tag{103, protopack.Fixed32Type}, protopack.Uint32(47),
902				protopack.Tag{104, protopack.Fixed64Type}, protopack.Int64(0xdeadbeef),
903			}.Marshal())
904			return m
905		}(),
906		want: `opt_string: "this message contains unknown fields"
907`,
908	}, {
909		desc: "unknown varint and fixed types",
910		mo:   prototext.MarshalOptions{EmitUnknown: true},
911		input: func() proto.Message {
912			m := &pb2.Scalars{
913				OptString: proto.String("this message contains unknown fields"),
914			}
915			m.ProtoReflect().SetUnknown(protopack.Message{
916				protopack.Tag{101, protopack.VarintType}, protopack.Bool(true),
917				protopack.Tag{102, protopack.VarintType}, protopack.Varint(0xff),
918				protopack.Tag{103, protopack.Fixed32Type}, protopack.Uint32(0x47),
919				protopack.Tag{104, protopack.Fixed64Type}, protopack.Int64(0xdeadbeef),
920			}.Marshal())
921			return m
922		}(),
923		want: `opt_string: "this message contains unknown fields"
924101: 1
925102: 255
926103: 0x47
927104: 0xdeadbeef
928`,
929	}, {
930		desc: "unknown length-delimited",
931		mo:   prototext.MarshalOptions{EmitUnknown: true},
932		input: func() proto.Message {
933			m := new(pb2.Scalars)
934			m.ProtoReflect().SetUnknown(protopack.Message{
935				protopack.Tag{101, protopack.BytesType}, protopack.LengthPrefix{protopack.Bool(true), protopack.Bool(false)},
936				protopack.Tag{102, protopack.BytesType}, protopack.String("hello world"),
937				protopack.Tag{103, protopack.BytesType}, protopack.Bytes("\xe4\xb8\x96\xe7\x95\x8c"),
938			}.Marshal())
939			return m
940		}(),
941		want: `101: "\x01\x00"
942102: "hello world"
943103: "世界"
944`,
945	}, {
946		desc: "unknown group type",
947		mo:   prototext.MarshalOptions{EmitUnknown: true},
948		input: func() proto.Message {
949			m := new(pb2.Scalars)
950			m.ProtoReflect().SetUnknown(protopack.Message{
951				protopack.Tag{101, protopack.StartGroupType}, protopack.Tag{101, protopack.EndGroupType},
952				protopack.Tag{102, protopack.StartGroupType},
953				protopack.Tag{101, protopack.VarintType}, protopack.Bool(false),
954				protopack.Tag{102, protopack.BytesType}, protopack.String("inside a group"),
955				protopack.Tag{102, protopack.EndGroupType},
956			}.Marshal())
957			return m
958		}(),
959		want: `101: {}
960102: {
961  101: 0
962  102: "inside a group"
963}
964`,
965	}, {
966		desc: "unknown unpack repeated field",
967		mo:   prototext.MarshalOptions{EmitUnknown: true},
968		input: func() proto.Message {
969			m := new(pb2.Scalars)
970			m.ProtoReflect().SetUnknown(protopack.Message{
971				protopack.Tag{101, protopack.BytesType}, protopack.LengthPrefix{protopack.Bool(true), protopack.Bool(false), protopack.Bool(true)},
972				protopack.Tag{102, protopack.BytesType}, protopack.String("hello"),
973				protopack.Tag{101, protopack.VarintType}, protopack.Bool(true),
974				protopack.Tag{102, protopack.BytesType}, protopack.String("世界"),
975			}.Marshal())
976			return m
977		}(),
978		want: `101: "\x01\x00\x01"
979102: "hello"
980101: 1
981102: "世界"
982`,
983	}, {
984		desc: "extensions of non-repeated fields",
985		input: func() proto.Message {
986			m := &pb2.Extensions{
987				OptString: proto.String("non-extension field"),
988				OptBool:   proto.Bool(true),
989				OptInt32:  proto.Int32(42),
990			}
991			proto.SetExtension(m, pb2.E_OptExtBool, true)
992			proto.SetExtension(m, pb2.E_OptExtString, "extension field")
993			proto.SetExtension(m, pb2.E_OptExtEnum, pb2.Enum_TEN)
994			proto.SetExtension(m, pb2.E_OptExtNested, &pb2.Nested{
995				OptString: proto.String("nested in an extension"),
996				OptNested: &pb2.Nested{
997					OptString: proto.String("another nested in an extension"),
998				},
999			})
1000			return m
1001		}(),
1002		want: `opt_string: "non-extension field"
1003opt_bool: true
1004opt_int32: 42
1005[pb2.opt_ext_bool]: true
1006[pb2.opt_ext_enum]: TEN
1007[pb2.opt_ext_nested]: {
1008  opt_string: "nested in an extension"
1009  opt_nested: {
1010    opt_string: "another nested in an extension"
1011  }
1012}
1013[pb2.opt_ext_string]: "extension field"
1014`,
1015	}, {
1016		desc: "proto2 extension field contains invalid UTF-8",
1017		input: func() proto.Message {
1018			m := &pb2.Extensions{}
1019			proto.SetExtension(m, pb2.E_OptExtString, "abc\xff")
1020			return m
1021		}(),
1022		want: `[pb2.opt_ext_string]: "abc\xff"
1023`,
1024	}, {
1025		desc: "extension partial returns error",
1026		input: func() proto.Message {
1027			m := &pb2.Extensions{}
1028			proto.SetExtension(m, pb2.E_OptExtPartial, &pb2.PartialRequired{
1029				OptString: proto.String("partial1"),
1030			})
1031			proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtPartial, &pb2.PartialRequired{
1032				OptString: proto.String("partial2"),
1033			})
1034			return m
1035		}(),
1036		want: `[pb2.ExtensionsContainer.opt_ext_partial]: {
1037  opt_string: "partial2"
1038}
1039[pb2.opt_ext_partial]: {
1040  opt_string: "partial1"
1041}
1042`,
1043		wantErr: true,
1044	}, {
1045		desc: "extension partial with AllowPartial",
1046		mo:   prototext.MarshalOptions{AllowPartial: true},
1047		input: func() proto.Message {
1048			m := &pb2.Extensions{}
1049			proto.SetExtension(m, pb2.E_OptExtPartial, &pb2.PartialRequired{
1050				OptString: proto.String("partial1"),
1051			})
1052			return m
1053		}(),
1054		want: `[pb2.opt_ext_partial]: {
1055  opt_string: "partial1"
1056}
1057`,
1058	}, {
1059		desc: "extensions of repeated fields",
1060		input: func() proto.Message {
1061			m := &pb2.Extensions{}
1062			proto.SetExtension(m, pb2.E_RptExtEnum, []pb2.Enum{pb2.Enum_TEN, 101, pb2.Enum_ONE})
1063			proto.SetExtension(m, pb2.E_RptExtFixed32, []uint32{42, 47})
1064			proto.SetExtension(m, pb2.E_RptExtNested, []*pb2.Nested{
1065				&pb2.Nested{OptString: proto.String("one")},
1066				&pb2.Nested{OptString: proto.String("two")},
1067				&pb2.Nested{OptString: proto.String("three")},
1068			})
1069			return m
1070		}(),
1071		want: `[pb2.rpt_ext_enum]: TEN
1072[pb2.rpt_ext_enum]: 101
1073[pb2.rpt_ext_enum]: ONE
1074[pb2.rpt_ext_fixed32]: 42
1075[pb2.rpt_ext_fixed32]: 47
1076[pb2.rpt_ext_nested]: {
1077  opt_string: "one"
1078}
1079[pb2.rpt_ext_nested]: {
1080  opt_string: "two"
1081}
1082[pb2.rpt_ext_nested]: {
1083  opt_string: "three"
1084}
1085`,
1086	}, {
1087		desc: "extensions of non-repeated fields in another message",
1088		input: func() proto.Message {
1089			m := &pb2.Extensions{}
1090			proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtBool, true)
1091			proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtString, "extension field")
1092			proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtEnum, pb2.Enum_TEN)
1093			proto.SetExtension(m, pb2.E_ExtensionsContainer_OptExtNested, &pb2.Nested{
1094				OptString: proto.String("nested in an extension"),
1095				OptNested: &pb2.Nested{
1096					OptString: proto.String("another nested in an extension"),
1097				},
1098			})
1099			return m
1100		}(),
1101		want: `[pb2.ExtensionsContainer.opt_ext_bool]: true
1102[pb2.ExtensionsContainer.opt_ext_enum]: TEN
1103[pb2.ExtensionsContainer.opt_ext_nested]: {
1104  opt_string: "nested in an extension"
1105  opt_nested: {
1106    opt_string: "another nested in an extension"
1107  }
1108}
1109[pb2.ExtensionsContainer.opt_ext_string]: "extension field"
1110`,
1111	}, {
1112		desc: "extensions of repeated fields in another message",
1113		input: func() proto.Message {
1114			m := &pb2.Extensions{
1115				OptString: proto.String("non-extension field"),
1116				OptBool:   proto.Bool(true),
1117				OptInt32:  proto.Int32(42),
1118			}
1119			proto.SetExtension(m, pb2.E_ExtensionsContainer_RptExtEnum, []pb2.Enum{pb2.Enum_TEN, 101, pb2.Enum_ONE})
1120			proto.SetExtension(m, pb2.E_ExtensionsContainer_RptExtString, []string{"hello", "world"})
1121			proto.SetExtension(m, pb2.E_ExtensionsContainer_RptExtNested, []*pb2.Nested{
1122				&pb2.Nested{OptString: proto.String("one")},
1123				&pb2.Nested{OptString: proto.String("two")},
1124				&pb2.Nested{OptString: proto.String("three")},
1125			})
1126			return m
1127		}(),
1128		want: `opt_string: "non-extension field"
1129opt_bool: true
1130opt_int32: 42
1131[pb2.ExtensionsContainer.rpt_ext_enum]: TEN
1132[pb2.ExtensionsContainer.rpt_ext_enum]: 101
1133[pb2.ExtensionsContainer.rpt_ext_enum]: ONE
1134[pb2.ExtensionsContainer.rpt_ext_nested]: {
1135  opt_string: "one"
1136}
1137[pb2.ExtensionsContainer.rpt_ext_nested]: {
1138  opt_string: "two"
1139}
1140[pb2.ExtensionsContainer.rpt_ext_nested]: {
1141  opt_string: "three"
1142}
1143[pb2.ExtensionsContainer.rpt_ext_string]: "hello"
1144[pb2.ExtensionsContainer.rpt_ext_string]: "world"
1145`,
1146	}, {
1147		desc: "MessageSet",
1148		input: func() proto.Message {
1149			m := &pb2.MessageSet{}
1150			proto.SetExtension(m, pb2.E_MessageSetExtension_MessageSetExtension, &pb2.MessageSetExtension{
1151				OptString: proto.String("a messageset extension"),
1152			})
1153			proto.SetExtension(m, pb2.E_MessageSetExtension_NotMessageSetExtension, &pb2.MessageSetExtension{
1154				OptString: proto.String("not a messageset extension"),
1155			})
1156			proto.SetExtension(m, pb2.E_MessageSetExtension_ExtNested, &pb2.Nested{
1157				OptString: proto.String("just a regular extension"),
1158			})
1159			return m
1160		}(),
1161		want: `[pb2.MessageSetExtension.ext_nested]: {
1162  opt_string: "just a regular extension"
1163}
1164[pb2.MessageSetExtension]: {
1165  opt_string: "a messageset extension"
1166}
1167[pb2.MessageSetExtension.not_message_set_extension]: {
1168  opt_string: "not a messageset extension"
1169}
1170`,
1171		skip: !flags.ProtoLegacy,
1172	}, {
1173		desc: "not real MessageSet 1",
1174		input: func() proto.Message {
1175			m := &pb2.FakeMessageSet{}
1176			proto.SetExtension(m, pb2.E_FakeMessageSetExtension_MessageSetExtension, &pb2.FakeMessageSetExtension{
1177				OptString: proto.String("not a messageset extension"),
1178			})
1179			return m
1180		}(),
1181		want: `[pb2.FakeMessageSetExtension.message_set_extension]: {
1182  opt_string: "not a messageset extension"
1183}
1184`,
1185		skip: !flags.ProtoLegacy,
1186	}, {
1187		desc: "not real MessageSet 2",
1188		input: func() proto.Message {
1189			m := &pb2.MessageSet{}
1190			proto.SetExtension(m, pb2.E_MessageSetExtension, &pb2.FakeMessageSetExtension{
1191				OptString: proto.String("another not a messageset extension"),
1192			})
1193			return m
1194		}(),
1195		want: `[pb2.message_set_extension]: {
1196  opt_string: "another not a messageset extension"
1197}
1198`,
1199		skip: !flags.ProtoLegacy,
1200	}, {
1201		desc: "Any not expanded",
1202		mo: prototext.MarshalOptions{
1203			Resolver: new(protoregistry.Types),
1204		},
1205		input: func() proto.Message {
1206			m := &pb2.Nested{
1207				OptString: proto.String("embedded inside Any"),
1208				OptNested: &pb2.Nested{
1209					OptString: proto.String("inception"),
1210				},
1211			}
1212			b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1213			if err != nil {
1214				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1215			}
1216			return &anypb.Any{
1217				TypeUrl: "pb2.Nested",
1218				Value:   b,
1219			}
1220		}(),
1221		want: `type_url: "pb2.Nested"
1222value: "\n\x13embedded inside Any\x12\x0b\n\tinception"
1223`,
1224	}, {
1225		desc: "Any expanded",
1226		input: func() proto.Message {
1227			m := &pb2.Nested{
1228				OptString: proto.String("embedded inside Any"),
1229				OptNested: &pb2.Nested{
1230					OptString: proto.String("inception"),
1231				},
1232			}
1233			b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1234			if err != nil {
1235				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1236			}
1237			return &anypb.Any{
1238				TypeUrl: "foo/pb2.Nested",
1239				Value:   b,
1240			}
1241		}(),
1242		want: `[foo/pb2.Nested]: {
1243  opt_string: "embedded inside Any"
1244  opt_nested: {
1245    opt_string: "inception"
1246  }
1247}
1248`,
1249	}, {
1250		desc: "Any expanded with missing required",
1251		input: func() proto.Message {
1252			m := &pb2.PartialRequired{
1253				OptString: proto.String("embedded inside Any"),
1254			}
1255			b, err := proto.MarshalOptions{
1256				AllowPartial:  true,
1257				Deterministic: true,
1258			}.Marshal(m)
1259			if err != nil {
1260				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1261			}
1262			return &anypb.Any{
1263				TypeUrl: string(m.ProtoReflect().Descriptor().FullName()),
1264				Value:   b,
1265			}
1266		}(),
1267		want: `[pb2.PartialRequired]: {
1268  opt_string: "embedded inside Any"
1269}
1270`,
1271	}, {
1272		desc: "Any with invalid value",
1273		input: &anypb.Any{
1274			TypeUrl: "foo/pb2.Nested",
1275			Value:   []byte("\x80"),
1276		},
1277		want: `type_url: "foo/pb2.Nested"
1278value: "\x80"
1279`,
1280	}, {
1281		desc: "Any expanded in another message",
1282		input: func() *pb2.KnownTypes {
1283			m1 := &pb2.Nested{
1284				OptString: proto.String("message inside Any of another Any field"),
1285			}
1286			b1, err := proto.MarshalOptions{Deterministic: true}.Marshal(m1)
1287			if err != nil {
1288				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1289			}
1290			m2 := &anypb.Any{
1291				TypeUrl: "pb2.Nested",
1292				Value:   b1,
1293			}
1294			b2, err := proto.MarshalOptions{Deterministic: true}.Marshal(m2)
1295			if err != nil {
1296				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1297			}
1298			return &pb2.KnownTypes{
1299				OptAny: &anypb.Any{
1300					TypeUrl: "google.protobuf.Any",
1301					Value:   b2,
1302				},
1303			}
1304		}(),
1305		want: `opt_any: {
1306  [google.protobuf.Any]: {
1307    [pb2.Nested]: {
1308      opt_string: "message inside Any of another Any field"
1309    }
1310  }
1311}
1312`,
1313	}, {
1314		desc: "Any expanded with invalid UTF-8 in proto2",
1315		input: func() *pb2.KnownTypes {
1316			m := &pb2.Nested{
1317				OptString: proto.String("invalid UTF-8 abc\xff"),
1318			}
1319			b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1320			if err != nil {
1321				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1322			}
1323			return &pb2.KnownTypes{
1324				OptAny: &anypb.Any{
1325					TypeUrl: "pb2.Nested",
1326					Value:   b,
1327				},
1328			}
1329		}(),
1330		want: `opt_any: {
1331  [pb2.Nested]: {
1332    opt_string: "invalid UTF-8 abc\xff"
1333  }
1334}
1335`,
1336	}, {
1337		desc: "Any not expanded due to invalid data",
1338		mo:   prototext.MarshalOptions{EmitASCII: true},
1339		input: func() *pb2.KnownTypes {
1340			return &pb2.KnownTypes{
1341				OptAny: &anypb.Any{
1342					TypeUrl: "pb3.Scalar",
1343					Value:   []byte("\xde\xad\xbe\xef"),
1344				},
1345			}
1346		}(),
1347		want: `opt_any: {
1348  type_url: "pb3.Scalar"
1349  value: "\u07ad\xbe\xef"
1350}
1351`,
1352	}, {
1353		desc: "Any inside Any expanded",
1354		input: func() *pb2.KnownTypes {
1355			m1 := &pb2.Nested{
1356				OptString: proto.String("invalid UTF-8 abc\xff"),
1357			}
1358			b1, err := proto.MarshalOptions{Deterministic: true}.Marshal(m1)
1359			if err != nil {
1360				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1361			}
1362			m2 := &anypb.Any{
1363				TypeUrl: "pb2.Nested",
1364				Value:   b1,
1365			}
1366			b2, err := proto.MarshalOptions{Deterministic: true}.Marshal(m2)
1367			if err != nil {
1368				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1369			}
1370			return &pb2.KnownTypes{
1371				OptAny: &anypb.Any{
1372					TypeUrl: "google.protobuf.Any",
1373					Value:   b2,
1374				},
1375			}
1376		}(),
1377		want: `opt_any: {
1378  [google.protobuf.Any]: {
1379    [pb2.Nested]: {
1380      opt_string: "invalid UTF-8 abc\xff"
1381    }
1382  }
1383}
1384`,
1385	}, {
1386		desc: "Any inside Any not expanded due to invalid data",
1387		mo:   prototext.MarshalOptions{EmitASCII: true},
1388		input: func() *pb2.KnownTypes {
1389			m := &anypb.Any{
1390				TypeUrl: "pb2.Nested",
1391				Value:   []byte("\xde\xad\xbe\xef"),
1392			}
1393			b, err := proto.MarshalOptions{Deterministic: true}.Marshal(m)
1394			if err != nil {
1395				t.Fatalf("error in binary marshaling message for Any.value: %v", err)
1396			}
1397			return &pb2.KnownTypes{
1398				OptAny: &anypb.Any{
1399					TypeUrl: "google.protobuf.Any",
1400					Value:   b,
1401				},
1402			}
1403		}(),
1404		want: `opt_any: {
1405  [google.protobuf.Any]: {
1406    type_url: "pb2.Nested"
1407    value: "\u07ad\xbe\xef"
1408  }
1409}
1410`,
1411	}}
1412
1413	for _, tt := range tests {
1414		tt := tt
1415		if tt.skip {
1416			continue
1417		}
1418		t.Run(tt.desc, func(t *testing.T) {
1419			// Use 2-space indentation on all MarshalOptions.
1420			tt.mo.Indent = "  "
1421			b, err := tt.mo.Marshal(tt.input)
1422			if err != nil && !tt.wantErr {
1423				t.Errorf("Marshal() returned error: %v\n", err)
1424			}
1425			if err == nil && tt.wantErr {
1426				t.Error("Marshal() got nil error, want error\n")
1427			}
1428			got := string(b)
1429			if tt.want != "" && got != tt.want {
1430				t.Errorf("Marshal()\n<got>\n%v\n<want>\n%v\n", got, tt.want)
1431				if diff := cmp.Diff(tt.want, got); diff != "" {
1432					t.Errorf("Marshal() diff -want +got\n%v\n", diff)
1433				}
1434			}
1435		})
1436	}
1437}
1438