blob: 7361e778338e7620fedff466b8153607abdac5a5 [file] [log] [blame]
Joe Tsai27c2a762018-08-01 16:48:18 -07001// 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 text
6
7import (
8 "fmt"
9 "math"
10 "strings"
11 "testing"
12 "unicode/utf8"
13
Joe Tsai71acbc72018-11-28 19:27:29 -080014 "github.com/golang/protobuf/v2/internal/detrand"
Joe Tsai01ab2962018-09-21 17:44:00 -070015 "github.com/golang/protobuf/v2/internal/flags"
16 "github.com/golang/protobuf/v2/reflect/protoreflect"
Joe Tsai27c2a762018-08-01 16:48:18 -070017 "github.com/google/go-cmp/cmp"
18 "github.com/google/go-cmp/cmp/cmpopts"
Joe Tsai27c2a762018-08-01 16:48:18 -070019)
20
Joe Tsai71acbc72018-11-28 19:27:29 -080021// Disable detrand to enable direct comparisons on outputs.
22func init() { detrand.Disable() }
23
Herbie Ongc3f4d482018-11-22 14:29:07 -080024var S = fmt.Sprintf
25var V = ValueOf
26var ID = func(n protoreflect.Name) Value { return V(n) }
27
28type Lst = []Value
29type Msg = [][2]Value
30
Joe Tsai27c2a762018-08-01 16:48:18 -070031func Test(t *testing.T) {
32 const space = " \n\r\t"
Joe Tsai27c2a762018-08-01 16:48:18 -070033
34 tests := []struct {
35 in string
36 wantVal Value
37 wantOut string
38 wantOutBracket string
39 wantOutASCII string
40 wantOutIndent string
41 wantErr string
42 }{{
43 in: "",
44 wantVal: V(Msg{}),
45 wantOutIndent: "\n",
46 }, {
47 in: S("%s# hello%s", space, space),
48 wantVal: V(Msg{}),
49 }, {
50 in: S("%s# hello\rfoo:bar", space),
51 wantVal: V(Msg{}),
52 }, {
53 // Comments only extend until the newline.
54 in: S("%s# hello\nfoo:bar", space),
55 wantVal: V(Msg{{ID("foo"), ID("bar")}}),
56 wantOut: "foo:bar",
57 wantOutIndent: "foo: bar\n",
58 }, {
59 // NUL is an invalid whitespace since C++ uses C-strings.
60 in: "\x00",
61 wantErr: `invalid "\x00" as identifier`,
62 }, {
63 in: "foo:0",
64 wantVal: V(Msg{{ID("foo"), V(uint32(0))}}),
65 wantOut: "foo:0",
66 }, {
67 in: S("%sfoo%s:0", space, space),
68 wantVal: V(Msg{{ID("foo"), V(uint32(0))}}),
69 }, {
70 in: "foo bar:0",
71 wantErr: `expected ':' after message key`,
72 }, {
73 in: "[foo]:0",
74 wantVal: V(Msg{{V("foo"), V(uint32(0))}}),
75 wantOut: "[foo]:0",
76 wantOutIndent: "[foo]: 0\n",
77 }, {
78 in: S("%s[%sfoo%s]%s:0", space, space, space, space),
79 wantVal: V(Msg{{V("foo"), V(uint32(0))}}),
80 }, {
81 in: "[proto.package.name]:0",
82 wantVal: V(Msg{{V("proto.package.name"), V(uint32(0))}}),
83 wantOut: "[proto.package.name]:0",
84 wantOutIndent: "[proto.package.name]: 0\n",
85 }, {
86 in: S("%s[%sproto.package.name%s]%s:0", space, space, space, space),
87 wantVal: V(Msg{{V("proto.package.name"), V(uint32(0))}}),
88 }, {
89 in: "['sub.domain.com\x2fpath\x2fto\x2fproto.package.name']:0",
90 wantVal: V(Msg{{V("sub.domain.com/path/to/proto.package.name"), V(uint32(0))}}),
91 wantOut: "[sub.domain.com/path/to/proto.package.name]:0",
92 wantOutIndent: "[sub.domain.com/path/to/proto.package.name]: 0\n",
93 }, {
94 in: "[\"sub.domain.com\x2fpath\x2fto\x2fproto.package.name\"]:0",
95 wantVal: V(Msg{{V("sub.domain.com/path/to/proto.package.name"), V(uint32(0))}}),
96 }, {
97 in: S("%s[%s'sub.domain.com\x2fpath\x2fto\x2fproto.package.name'%s]%s:0", space, space, space, space),
98 wantVal: V(Msg{{V("sub.domain.com/path/to/proto.package.name"), V(uint32(0))}}),
99 }, {
100 in: S("%s[%s\"sub.domain.com\x2fpath\x2fto\x2fproto.package.name\"%s]%s:0", space, space, space, space),
101 wantVal: V(Msg{{V("sub.domain.com/path/to/proto.package.name"), V(uint32(0))}}),
102 }, {
103 in: `['http://example.com/path/to/proto.package.name']:0`,
104 wantVal: V(Msg{{V("http://example.com/path/to/proto.package.name"), V(uint32(0))}}),
105 wantOut: `["http://example.com/path/to/proto.package.name"]:0`,
106 wantOutIndent: `["http://example.com/path/to/proto.package.name"]: 0` + "\n",
107 }, {
108 in: "[proto.package.name:0",
109 wantErr: `invalid character ':', expected ']' at end of extension name`,
110 }, {
111 in: "[proto.package name]:0",
112 wantErr: `invalid character 'n', expected ']' at end of extension name`,
113 }, {
114 in: `["proto.package" "name"]:0`,
115 wantErr: `invalid character '"', expected ']' at end of extension name`,
116 }, {
117 in: `["\z"]`,
118 wantErr: `invalid escape code "\\z" in string`,
119 }, {
120 in: "[$]",
121 wantErr: `invalid "$" as identifier`,
122 }, {
123 // This parses fine, but should result in a error later since no
124 // type name in proto will ever be just a number.
125 in: "[20]:0",
126 wantVal: V(Msg{{V("20"), V(uint32(0))}}),
127 wantOut: "[20]:0",
128 }, {
129 in: "20:0",
130 wantVal: V(Msg{{V(uint32(20)), V(uint32(0))}}),
131 wantOut: "20:0",
132 }, {
133 in: "0x20:0",
134 wantVal: V(Msg{{V(uint32(0x20)), V(uint32(0))}}),
135 wantOut: "32:0",
136 }, {
137 in: "020:0",
138 wantVal: V(Msg{{V(uint32(020)), V(uint32(0))}}),
139 wantOut: "16:0",
140 }, {
141 in: "-20:0",
142 wantErr: `invalid "-20" as identifier`,
143 }, {
144 in: `foo:true bar:"s" baz:{} qux:[] wib:id`,
145 wantVal: V(Msg{
146 {ID("foo"), V(true)},
147 {ID("bar"), V("s")},
148 {ID("baz"), V(Msg{})},
149 {ID("qux"), V(Lst{})},
150 {ID("wib"), ID("id")},
151 }),
152 wantOut: `foo:true bar:"s" baz:{} qux:[] wib:id`,
153 wantOutIndent: "foo: true\nbar: \"s\"\nbaz: {}\nqux: []\nwib: id\n",
154 }, {
155 in: S(`%sfoo%s:%strue%s %sbar%s:%s"s"%s %sbaz%s:%s<>%s %squx%s:%s[]%s %swib%s:%sid%s`,
156 space, space, space, space, space, space, space, space, space, space, space, space, space, space, space, space, space, space, space, space),
157 wantVal: V(Msg{
158 {ID("foo"), V(true)},
159 {ID("bar"), V("s")},
160 {ID("baz"), V(Msg{})},
161 {ID("qux"), V(Lst{})},
162 {ID("wib"), ID("id")},
163 }),
164 }, {
165 in: `foo:true;`,
166 wantVal: V(Msg{{ID("foo"), V(true)}}),
167 wantOut: "foo:true",
168 wantOutIndent: "foo: true\n",
169 }, {
170 in: `foo:true,`,
171 wantVal: V(Msg{{ID("foo"), V(true)}}),
172 }, {
173 in: `foo:bar;,`,
174 wantErr: `invalid "," as identifier`,
175 }, {
176 in: `foo:bar,;`,
177 wantErr: `invalid ";" as identifier`,
178 }, {
179 in: `footrue`,
180 wantErr: `unexpected EOF`,
181 }, {
182 in: `foo true`,
183 wantErr: `expected ':' after message key`,
184 }, {
185 in: `foo"s"`,
186 wantErr: `expected ':' after message key`,
187 }, {
188 in: `foo "s"`,
189 wantErr: `expected ':' after message key`,
190 }, {
191 in: `foo{}`,
192 wantVal: V(Msg{{ID("foo"), V(Msg{})}}),
193 wantOut: "foo:{}",
194 wantOutBracket: "foo:<>",
195 wantOutIndent: "foo: {}\n",
196 }, {
197 in: `foo {}`,
198 wantVal: V(Msg{{ID("foo"), V(Msg{})}}),
199 }, {
200 in: `foo<>`,
201 wantVal: V(Msg{{ID("foo"), V(Msg{})}}),
202 }, {
203 in: `foo <>`,
204 wantVal: V(Msg{{ID("foo"), V(Msg{})}}),
205 }, {
206 in: `foo[]`,
207 wantErr: `expected ':' after message key`,
208 }, {
209 in: `foo []`,
210 wantErr: `expected ':' after message key`,
211 }, {
212 in: `foo:truebar:true`,
213 wantErr: `invalid ":" as identifier`,
214 }, {
215 in: `foo:"s"bar:true`,
216 wantVal: V(Msg{{ID("foo"), V("s")}, {ID("bar"), V(true)}}),
217 wantOut: `foo:"s" bar:true`,
218 wantOutIndent: "foo: \"s\"\nbar: true\n",
219 }, {
220 in: `foo:0bar:true`,
221 wantErr: `invalid "0bar" as number or bool`,
222 }, {
223 in: `foo:{}bar:true`,
224 wantVal: V(Msg{{ID("foo"), V(Msg{})}, {ID("bar"), V(true)}}),
225 wantOut: "foo:{} bar:true",
226 wantOutBracket: "foo:<> bar:true",
227 wantOutIndent: "foo: {}\nbar: true\n",
228 }, {
229 in: `foo:[]bar:true`,
230 wantVal: V(Msg{{ID("foo"), V(Lst{})}, {ID("bar"), V(true)}}),
231 }, {
232 in: `foo{bar:true}`,
233 wantVal: V(Msg{{ID("foo"), V(Msg{{ID("bar"), V(true)}})}}),
234 wantOut: "foo:{bar:true}",
235 wantOutBracket: "foo:<bar:true>",
236 wantOutIndent: "foo: {\n\tbar: true\n}\n",
237 }, {
238 in: `foo<bar:true>`,
239 wantVal: V(Msg{{ID("foo"), V(Msg{{ID("bar"), V(true)}})}}),
240 }, {
241 in: `foo{bar:true,}`,
242 wantVal: V(Msg{{ID("foo"), V(Msg{{ID("bar"), V(true)}})}}),
243 }, {
244 in: `foo{bar:true;}`,
245 wantVal: V(Msg{{ID("foo"), V(Msg{{ID("bar"), V(true)}})}}),
246 }, {
247 in: `foo{`,
248 wantErr: `unexpected EOF`,
249 }, {
250 in: `foo{ `,
251 wantErr: `unexpected EOF`,
252 }, {
253 in: `foo{[`,
254 wantErr: `unexpected EOF`,
255 }, {
256 in: `foo{[ `,
257 wantErr: `unexpected EOF`,
258 }, {
259 in: `foo{bar:true,;}`,
260 wantErr: `invalid ";" as identifier`,
261 }, {
262 in: `foo{bar:true;,}`,
263 wantErr: `invalid "," as identifier`,
264 }, {
265 in: `foo<bar:{}>`,
266 wantVal: V(Msg{{ID("foo"), V(Msg{{ID("bar"), V(Msg{})}})}}),
267 wantOut: "foo:{bar:{}}",
268 wantOutBracket: "foo:<bar:<>>",
269 wantOutIndent: "foo: {\n\tbar: {}\n}\n",
270 }, {
271 in: `foo<bar:{>`,
272 wantErr: `invalid character '>', expected '}' at end of message`,
273 }, {
274 in: `foo<bar:{}`,
275 wantErr: `unexpected EOF`,
276 }, {
277 in: `arr:[]`,
278 wantVal: V(Msg{{ID("arr"), V(Lst{})}}),
279 wantOut: "arr:[]",
280 wantOutBracket: "arr:[]",
281 wantOutIndent: "arr: []\n",
282 }, {
283 in: `arr:[,]`,
284 wantErr: `invalid "," as number or bool`,
285 }, {
286 in: `arr:[0 0]`,
287 wantErr: `invalid character '0', expected ']' at end of list`,
288 }, {
289 in: `arr:["foo" "bar"]`,
290 wantVal: V(Msg{{ID("arr"), V(Lst{V("foobar")})}}),
291 wantOut: `arr:["foobar"]`,
292 wantOutBracket: `arr:["foobar"]`,
293 wantOutIndent: "arr: [\n\t\"foobar\"\n]\n",
294 }, {
295 in: `arr:[0,]`,
296 wantErr: `invalid "]" as number or bool`,
297 }, {
298 in: `arr:[true,0,"",id,[],{}]`,
299 wantVal: V(Msg{{ID("arr"), V(Lst{
300 V(true), V(uint32(0)), V(""), ID("id"), V(Lst{}), V(Msg{}),
301 })}}),
302 wantOut: `arr:[true,0,"",id,[],{}]`,
303 wantOutBracket: `arr:[true,0,"",id,[],<>]`,
304 wantOutIndent: "arr: [\n\ttrue,\n\t0,\n\t\"\",\n\tid,\n\t[],\n\t{}\n]\n",
305 }, {
306 in: S(`arr:[%strue%s,%s0%s,%s""%s,%sid%s,%s[]%s,%s{}%s]`,
307 space, space, space, space, space, space, space, space, space, space, space, space),
308 wantVal: V(Msg{{ID("arr"), V(Lst{
309 V(true), V(uint32(0)), V(""), ID("id"), V(Lst{}), V(Msg{}),
310 })}}),
311 }, {
312 in: `arr:[`,
313 wantErr: `unexpected EOF`,
314 }, {
315 in: `{`,
316 wantErr: `invalid "{" as identifier`,
317 }, {
318 in: `<`,
319 wantErr: `invalid "<" as identifier`,
320 }, {
321 in: `[`,
322 wantErr: "unexpected EOF",
323 }, {
324 in: `}`,
325 wantErr: "1 bytes of unconsumed input",
326 }, {
327 in: `>`,
328 wantErr: "1 bytes of unconsumed input",
329 }, {
330 in: `]`,
331 wantErr: `invalid "]" as identifier`,
332 }, {
333 in: `str: "'"`,
334 wantVal: V(Msg{{ID("str"), V(`'`)}}),
335 wantOut: `str:"'"`,
336 }, {
337 in: `str: '"'`,
338 wantVal: V(Msg{{ID("str"), V(`"`)}}),
339 wantOut: `str:"\""`,
340 }, {
341 // String that has as few escaped characters as possible.
342 in: `str: ` + func() string {
343 var b []byte
344 for i := 0; i < utf8.RuneSelf; i++ {
345 switch i {
346 case 0, '\\', '\n', '\'': // these must be escaped, so ignore them
347 default:
348 b = append(b, byte(i))
349 }
350 }
351 return "'" + string(b) + "'"
352 }(),
353 wantVal: V(Msg{{ID("str"), V("\x01\x02\x03\x04\x05\x06\a\b\t\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f")}}),
354 wantOut: `str:"\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\"",
355 wantOutASCII: `str:"\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\"",
356 }, {
357 in: "str: '\xde\xad\xbe\xef'",
358 wantVal: V(Msg{{ID("str"), V("\xde\xad\xbe\xef")}}),
359 wantOut: "str:\"\u07ad\\xbe\\xef\"",
360 wantOutASCII: `str:"\u07ad\xbe\xef"`,
361 wantErr: "invalid UTF-8 detected",
362 }, {
363 // Valid UTF-8 wire encoding, but sub-optimal encoding.
364 in: "str: '\xc0\x80'",
365 wantVal: V(Msg{{ID("str"), V("\xc0\x80")}}),
366 wantOut: `str:"\xc0\x80"`,
367 wantOutASCII: `str:"\xc0\x80"`,
368 wantErr: "invalid UTF-8 detected",
369 }, {
370 // Valid UTF-8 wire encoding, but invalid rune (surrogate pair).
371 in: "str: '\xed\xa0\x80'",
372 wantVal: V(Msg{{ID("str"), V("\xed\xa0\x80")}}),
373 wantOut: `str:"\xed\xa0\x80"`,
374 wantOutASCII: `str:"\xed\xa0\x80"`,
375 wantErr: "invalid UTF-8 detected",
376 }, {
377 // Valid UTF-8 wire encoding, but invalid rune (above max rune).
378 in: "str: '\xf7\xbf\xbf\xbf'",
379 wantVal: V(Msg{{ID("str"), V("\xf7\xbf\xbf\xbf")}}),
380 wantOut: `str:"\xf7\xbf\xbf\xbf"`,
381 wantOutASCII: `str:"\xf7\xbf\xbf\xbf"`,
382 wantErr: "invalid UTF-8 detected",
383 }, {
384 // Valid UTF-8 wire encoding of the RuneError rune.
385 in: "str: '\xef\xbf\xbd'",
386 wantVal: V(Msg{{ID("str"), V(string(utf8.RuneError))}}),
387 wantOut: `str:"` + string(utf8.RuneError) + `"`,
388 wantOutASCII: `str:"\ufffd"`,
389 }, {
390 in: "str: 'hello\u1234world'",
391 wantVal: V(Msg{{ID("str"), V("hello\u1234world")}}),
392 wantOut: "str:\"hello\u1234world\"",
393 wantOutASCII: `str:"hello\u1234world"`,
394 }, {
395 in: `str: '\"\'\\\?\a\b\n\r\t\v\f\1\12\123\xA\xaB\x12\uAb8f\U0010FFFF'`,
396 wantVal: V(Msg{{ID("str"), V("\"'\\?\a\b\n\r\t\v\f\x01\nS\n\xab\x12\uab8f\U0010ffff")}}),
397 wantOut: `str:"\"'\\?\x07\x08\n\r\t\x0b\x0c\x01\nS\n\xab\x12` + "\uab8f\U0010ffff" + `"`,
398 wantOutASCII: `str:"\"'\\?\x07\x08\n\r\t\x0b\x0c\x01\nS\n\xab\x12\uab8f\U0010ffff"`,
399 }, {
400 in: `str: '`,
401 wantErr: `unexpected EOF`,
402 }, {
403 in: `str: '\`,
404 wantErr: `unexpected EOF`,
405 }, {
406 in: `str: '\'`,
407 wantErr: `unexpected EOF`,
408 }, {
409 in: `str: '\8'`,
410 wantErr: `invalid escape code "\\8" in string`,
411 }, {
412 in: `str: '\1x'`,
413 wantVal: V(Msg{{ID("str"), V("\001x")}}),
414 wantOut: `str:"\x01x"`,
415 wantOutASCII: `str:"\x01x"`,
416 }, {
417 in: `str: '\12x'`,
418 wantVal: V(Msg{{ID("str"), V("\012x")}}),
419 wantOut: `str:"\nx"`,
420 wantOutASCII: `str:"\nx"`,
421 }, {
422 in: `str: '\123x'`,
423 wantVal: V(Msg{{ID("str"), V("\123x")}}),
424 wantOut: `str:"Sx"`,
425 wantOutASCII: `str:"Sx"`,
426 }, {
427 in: `str: '\1234x'`,
428 wantVal: V(Msg{{ID("str"), V("\1234x")}}),
429 wantOut: `str:"S4x"`,
430 wantOutASCII: `str:"S4x"`,
431 }, {
432 in: `str: '\1'`,
433 wantVal: V(Msg{{ID("str"), V("\001")}}),
434 wantOut: `str:"\x01"`,
435 wantOutASCII: `str:"\x01"`,
436 }, {
437 in: `str: '\12'`,
438 wantVal: V(Msg{{ID("str"), V("\012")}}),
439 wantOut: `str:"\n"`,
440 wantOutASCII: `str:"\n"`,
441 }, {
442 in: `str: '\123'`,
443 wantVal: V(Msg{{ID("str"), V("\123")}}),
444 wantOut: `str:"S"`,
445 wantOutASCII: `str:"S"`,
446 }, {
447 in: `str: '\1234'`,
448 wantVal: V(Msg{{ID("str"), V("\1234")}}),
449 wantOut: `str:"S4"`,
450 wantOutASCII: `str:"S4"`,
451 }, {
452 in: `str: '\377'`,
453 wantVal: V(Msg{{ID("str"), V("\377")}}),
454 wantOut: `str:"\xff"`,
455 wantOutASCII: `str:"\xff"`,
456 }, {
457 // Overflow octal escape.
458 in: `str: '\400'`,
459 wantErr: `invalid octal escape code "\\400" in string`,
460 }, {
461 in: `str: '\xfx'`,
462 wantVal: V(Msg{{ID("str"), V("\x0fx")}}),
463 wantOut: `str:"\x0fx"`,
464 wantOutASCII: `str:"\x0fx"`,
465 }, {
466 in: `str: '\xffx'`,
467 wantVal: V(Msg{{ID("str"), V("\xffx")}}),
468 wantOut: `str:"\xffx"`,
469 wantOutASCII: `str:"\xffx"`,
470 }, {
471 in: `str: '\xfffx'`,
472 wantVal: V(Msg{{ID("str"), V("\xfffx")}}),
473 wantOut: `str:"\xfffx"`,
474 wantOutASCII: `str:"\xfffx"`,
475 }, {
476 in: `str: '\xf'`,
477 wantVal: V(Msg{{ID("str"), V("\x0f")}}),
478 wantOut: `str:"\x0f"`,
479 wantOutASCII: `str:"\x0f"`,
480 }, {
481 in: `str: '\xff'`,
482 wantVal: V(Msg{{ID("str"), V("\xff")}}),
483 wantOut: `str:"\xff"`,
484 wantOutASCII: `str:"\xff"`,
485 }, {
486 in: `str: '\xfff'`,
487 wantVal: V(Msg{{ID("str"), V("\xfff")}}),
488 wantOut: `str:"\xfff"`,
489 wantOutASCII: `str:"\xfff"`,
490 }, {
491 in: `str: '\xz'`,
492 wantErr: `invalid hex escape code "\\x" in string`,
493 }, {
494 in: `str: '\uPo'`,
495 wantErr: `unexpected EOF`,
496 }, {
497 in: `str: '\uPoo'`,
498 wantErr: `invalid Unicode escape code "\\uPoo'" in string`,
499 }, {
500 in: `str: '\uPoop'`,
501 wantErr: `invalid Unicode escape code "\\uPoop" in string`,
502 }, {
503 // Unmatched surrogate pair.
504 in: `str: '\uDEAD'`,
505 wantErr: `unexpected EOF`, // trying to reader other half
506 }, {
507 // Surrogate pair with invalid other half.
508 in: `str: '\uDEAD\u0000'`,
509 wantErr: `invalid Unicode escape code "\\u0000" in string`,
510 }, {
511 // Properly matched surrogate pair.
512 in: `str: '\uD800\uDEAD'`,
513 wantVal: V(Msg{{ID("str"), V("𐊭")}}),
514 wantOut: `str:"𐊭"`,
515 wantOutASCII: `str:"\U000102ad"`,
516 }, {
517 // Overflow on Unicode rune.
518 in: `str: '\U00110000'`,
519 wantErr: `invalid Unicode escape code "\\U00110000" in string`,
520 }, {
521 in: `str: '\z'`,
522 wantErr: `invalid escape code "\\z" in string`,
523 }, {
524 // Strings cannot have NUL literal since C-style strings forbid them.
525 in: "str: '\x00'",
526 wantErr: `invalid character '\x00' in string`,
527 }, {
528 // Strings cannot have newline literal. The C++ permits them if an
529 // option is specified to allow them. In Go, we always forbid them.
530 in: "str: '\n'",
531 wantErr: `invalid character '\n' in string`,
532 }, {
533 in: "name: \"My name is \"\n\"elsewhere\"",
534 wantVal: V(Msg{{ID("name"), V("My name is elsewhere")}}),
535 wantOut: `name:"My name is elsewhere"`,
536 wantOutASCII: `name:"My name is elsewhere"`,
537 }, {
538 in: "name: 'My name is '\n'elsewhere'",
539 wantVal: V(Msg{{ID("name"), V("My name is elsewhere")}}),
540 }, {
541 in: "name: 'My name is '\n\"elsewhere\"",
542 wantVal: V(Msg{{ID("name"), V("My name is elsewhere")}}),
543 }, {
544 in: "name: \"My name is \"\n'elsewhere'",
545 wantVal: V(Msg{{ID("name"), V("My name is elsewhere")}}),
546 }, {
547 in: "name: \"My \"'name '\"is \"\n'elsewhere'",
548 wantVal: V(Msg{{ID("name"), V("My name is elsewhere")}}),
549 }, {
550 in: `crazy:"x'"'\""\''"'z"`,
551 wantVal: V(Msg{{ID("crazy"), V(`x'""''z`)}}),
552 }, {
Herbie Ong84f09602019-01-17 19:31:47 -0800553 in: `num: 1.02`,
554 wantVal: V(Msg{{ID("num"), V(float32(1.02))}}), // Use float32 to test marshaling of Float32 type.
555 wantOut: `num:1.02`,
556 }, {
Joe Tsai27c2a762018-08-01 16:48:18 -0700557 in: `nums: [t,T,true,True,TRUE,f,F,false,False,FALSE]`,
558 wantVal: V(Msg{{ID("nums"), V(Lst{
559 V(true),
560 ID("T"),
561 V(true),
562 V(true),
563 ID("TRUE"),
564 V(false),
565 ID("F"),
566 V(false),
567 V(false),
568 ID("FALSE"),
569 })}}),
570 wantOut: "nums:[true,T,true,true,TRUE,false,F,false,false,FALSE]",
571 wantOutIndent: "nums: [\n\ttrue,\n\tT,\n\ttrue,\n\ttrue,\n\tTRUE,\n\tfalse,\n\tF,\n\tfalse,\n\tfalse,\n\tFALSE\n]\n",
572 }, {
573 in: `nums: [nan,inf,-inf,NaN,NAN,Inf,INF]`,
574 wantVal: V(Msg{{ID("nums"), V(Lst{
575 V(math.NaN()),
576 V(math.Inf(+1)),
577 V(math.Inf(-1)),
578 ID("NaN"),
579 ID("NAN"),
580 ID("Inf"),
581 ID("INF"),
582 })}}),
583 wantOut: "nums:[nan,inf,-inf,NaN,NAN,Inf,INF]",
584 wantOutIndent: "nums: [\n\tnan,\n\tinf,\n\t-inf,\n\tNaN,\n\tNAN,\n\tInf,\n\tINF\n]\n",
585 }, {
586 // C++ permits this, but we currently reject this.
587 in: `num: -nan`,
588 wantErr: `invalid "-nan" as number or bool`,
589 }, {
590 in: `nums: [0,-0,-9876543210,9876543210,0x0,0x0123456789abcdef,-0x0123456789abcdef,01234567,-01234567]`,
591 wantVal: V(Msg{{ID("nums"), V(Lst{
592 V(uint32(0)),
593 V(int32(-0)),
594 V(int64(-9876543210)),
595 V(uint64(9876543210)),
596 V(uint32(0x0)),
597 V(uint64(0x0123456789abcdef)),
598 V(int64(-0x0123456789abcdef)),
599 V(uint64(01234567)),
600 V(int64(-01234567)),
601 })}}),
602 wantOut: "nums:[0,0,-9876543210,9876543210,0,81985529216486895,-81985529216486895,342391,-342391]",
603 wantOutIndent: "nums: [\n\t0,\n\t0,\n\t-9876543210,\n\t9876543210,\n\t0,\n\t81985529216486895,\n\t-81985529216486895,\n\t342391,\n\t-342391\n]\n",
604 }, {
605 in: `nums: [0.,0f,1f,10f,-0f,-1f,-10f,1.0,0.1e-3,1.5e+5,1e10,.0]`,
606 wantVal: V(Msg{{ID("nums"), V(Lst{
607 V(0.0),
608 V(0.0),
609 V(1.0),
610 V(10.0),
611 V(-0.0),
612 V(-1.0),
613 V(-10.0),
614 V(1.0),
615 V(0.1e-3),
616 V(1.5e+5),
617 V(1.0e+10),
618 V(0.0),
619 })}}),
620 wantOut: "nums:[0,0,1,10,0,-1,-10,1,0.0001,150000,1e+10,0]",
621 wantOutIndent: "nums: [\n\t0,\n\t0,\n\t1,\n\t10,\n\t0,\n\t-1,\n\t-10,\n\t1,\n\t0.0001,\n\t150000,\n\t1e+10,\n\t0\n]\n",
622 }, {
623 in: `nums: [0xbeefbeef,0xbeefbeefbeefbeef]`,
624 wantVal: V(Msg{{ID("nums"), func() Value {
625 if flags.Proto1Legacy {
626 return V(Lst{V(int32(-1091584273)), V(int64(-4688318750159552785))})
627 } else {
628 return V(Lst{V(uint32(0xbeefbeef)), V(uint64(0xbeefbeefbeefbeef))})
629 }
630 }()}}),
631 }, {
632 in: `num: +0`,
633 wantErr: `invalid "+0" as number or bool`,
634 }, {
635 in: `num: 01.1234`,
636 wantErr: `invalid "01.1234" as number or bool`,
637 }, {
638 in: `num: 0x`,
639 wantErr: `invalid "0x" as number or bool`,
640 }, {
641 in: `num: 0xX`,
642 wantErr: `invalid "0xX" as number or bool`,
643 }, {
644 in: `num: 0800`,
645 wantErr: `invalid "0800" as number or bool`,
646 }, {
647 in: `num: true.`,
648 wantErr: `invalid "true." as number or bool`,
649 }, {
650 in: `num: .`,
651 wantErr: `parsing ".": invalid syntax`,
652 }, {
653 in: `num: -.`,
654 wantErr: `parsing "-.": invalid syntax`,
655 }, {
656 in: `num: 1e10000`,
657 wantErr: `parsing "1e10000": value out of range`,
658 }, {
659 in: `num: 99999999999999999999`,
660 wantErr: `parsing "99999999999999999999": value out of range`,
661 }, {
662 in: `num: -99999999999999999999`,
663 wantErr: `parsing "-99999999999999999999": value out of range`,
664 }, {
665 in: "x: -",
666 wantErr: `syntax error (line 1:5)`,
667 }, {
668 in: "x:[\"💩\"x",
669 wantErr: `syntax error (line 1:7)`,
670 }, {
671 in: "x:\n\n[\"🔥🔥🔥\"x",
672 wantErr: `syntax error (line 3:7)`,
673 }, {
674 in: "x:[\"👍🏻👍🏿\"x",
675 wantErr: `syntax error (line 1:10)`, // multi-rune emojis; could be column:8
676 }, {
677 in: `
678 firstName : "John",
679 lastName : "Smith" ,
680 isAlive : true,
681 age : 27,
682 address { # missing colon is okay for messages
683 streetAddress : "21 2nd Street" ,
684 city : "New York" ,
685 state : "NY" ,
686 postalCode : "10021-3100" ; # trailing semicolon is okay
687 },
688 phoneNumbers : [ {
689 type : "home" ,
690 number : "212 555-1234"
691 } , {
692 type : "office" ,
693 number : "646 555-4567"
694 } , {
695 type : "mobile" ,
696 number : "123 456-7890" , # trailing comma is okay
697 } ],
698 children : [] ,
699 spouse : null`,
700 wantVal: V(Msg{
701 {ID("firstName"), V("John")},
702 {ID("lastName"), V("Smith")},
703 {ID("isAlive"), V(true)},
704 {ID("age"), V(27.0)},
705 {ID("address"), V(Msg{
706 {ID("streetAddress"), V("21 2nd Street")},
707 {ID("city"), V("New York")},
708 {ID("state"), V("NY")},
709 {ID("postalCode"), V("10021-3100")},
710 })},
711 {ID("phoneNumbers"), V([]Value{
712 V(Msg{
713 {ID("type"), V("home")},
714 {ID("number"), V("212 555-1234")},
715 }),
716 V(Msg{
717 {ID("type"), V("office")},
718 {ID("number"), V("646 555-4567")},
719 }),
720 V(Msg{
721 {ID("type"), V("mobile")},
722 {ID("number"), V("123 456-7890")},
723 }),
724 })},
725 {ID("children"), V([]Value{})},
726 {ID("spouse"), V(protoreflect.Name("null"))},
727 }),
728 wantOut: `firstName:"John" lastName:"Smith" isAlive:true age:27 address:{streetAddress:"21 2nd Street" city:"New York" state:"NY" postalCode:"10021-3100"} phoneNumbers:[{type:"home" number:"212 555-1234"},{type:"office" number:"646 555-4567"},{type:"mobile" number:"123 456-7890"}] children:[] spouse:null`,
729 wantOutBracket: `firstName:"John" lastName:"Smith" isAlive:true age:27 address:<streetAddress:"21 2nd Street" city:"New York" state:"NY" postalCode:"10021-3100"> phoneNumbers:[<type:"home" number:"212 555-1234">,<type:"office" number:"646 555-4567">,<type:"mobile" number:"123 456-7890">] children:[] spouse:null`,
730 wantOutIndent: `firstName: "John"
731lastName: "Smith"
732isAlive: true
733age: 27
734address: {
735 streetAddress: "21 2nd Street"
736 city: "New York"
737 state: "NY"
738 postalCode: "10021-3100"
739}
740phoneNumbers: [
741 {
742 type: "home"
743 number: "212 555-1234"
744 },
745 {
746 type: "office"
747 number: "646 555-4567"
748 },
749 {
750 type: "mobile"
751 number: "123 456-7890"
752 }
753]
754children: []
755spouse: null
756`,
757 }}
758
759 opts := cmp.Options{
760 cmpopts.EquateEmpty(),
761
762 // Transform composites (List and Message).
763 cmp.FilterValues(func(x, y Value) bool {
764 return (x.Type() == List && y.Type() == List) || (x.Type() == Message && y.Type() == Message)
765 }, cmp.Transformer("", func(v Value) interface{} {
766 if v.Type() == List {
767 return v.List()
768 } else {
769 return v.Message()
770 }
771 })),
772
773 // Compare scalars (Bool, Int, Uint, Float, String, Name).
774 cmp.FilterValues(func(x, y Value) bool {
775 return !(x.Type() == List && y.Type() == List) && !(x.Type() == Message && y.Type() == Message)
776 }, cmp.Comparer(func(x, y Value) bool {
777 if x.Type() == List || x.Type() == Message || y.Type() == List || y.Type() == Message {
778 return false
779 }
780 // Ensure golden value is always in x variable.
781 if len(x.raw) > 0 {
782 x, y = y, x
783 }
784 switch x.Type() {
785 case Bool:
786 want, _ := x.Bool()
787 got, ok := y.Bool()
788 return got == want && ok
789 case Int:
790 want, _ := x.Int(true)
791 got, ok := y.Int(want < math.MinInt32 || math.MaxInt32 < want)
792 return got == want && ok
793 case Uint:
794 want, _ := x.Uint(true)
795 got, ok := y.Uint(math.MaxUint32 < want)
796 return got == want && ok
Herbie Ong84f09602019-01-17 19:31:47 -0800797 case Float32:
798 want, _ := x.Float32()
799 got, ok := y.Float32()
800 if math.IsNaN(float64(got)) || math.IsNaN(float64(want)) {
801 return math.IsNaN(float64(got)) == math.IsNaN(float64(want))
802 }
803 return got == want && ok
804 case Float64:
805 want, _ := x.Float64()
806 got, ok := y.Float64()
Joe Tsai27c2a762018-08-01 16:48:18 -0700807 if math.IsNaN(got) || math.IsNaN(want) {
808 return math.IsNaN(got) == math.IsNaN(want)
809 }
810 return got == want && ok
811 case Name:
812 want, _ := x.Name()
813 got, ok := y.Name()
814 return got == want && ok
815 default:
816 return x.String() == y.String()
817 }
818 })),
819 }
820 for _, tt := range tests {
821 t.Run("", func(t *testing.T) {
822 if tt.in != "" || tt.wantVal.Type() != 0 || tt.wantErr != "" {
823 gotVal, err := Unmarshal([]byte(tt.in))
824 if err == nil {
825 if tt.wantErr != "" {
826 t.Errorf("Unmarshal(): got nil error, want %v", tt.wantErr)
827 }
828 } else {
829 if tt.wantErr == "" {
830 t.Errorf("Unmarshal(): got %v, want nil error", err)
831 } else if !strings.Contains(err.Error(), tt.wantErr) {
832 t.Errorf("Unmarshal(): got %v, want %v", err, tt.wantErr)
833 }
834 }
835 if diff := cmp.Diff(gotVal, tt.wantVal, opts); diff != "" {
836 t.Errorf("Unmarshal(): output mismatch (-got +want):\n%s", diff)
837 }
838 }
839 if tt.wantOut != "" {
840 gotOut, err := Marshal(tt.wantVal, "", [2]byte{0, 0}, false)
841 if err != nil {
842 t.Errorf("Marshal(): got %v, want nil error", err)
843 }
Joe Tsai71acbc72018-11-28 19:27:29 -0800844 if string(gotOut) != tt.wantOut {
Joe Tsai27c2a762018-08-01 16:48:18 -0700845 t.Errorf("Marshal():\ngot: %s\nwant: %s", gotOut, tt.wantOut)
846 }
847 }
848 if tt.wantOutBracket != "" {
849 gotOut, err := Marshal(tt.wantVal, "", [2]byte{'<', '>'}, false)
850 if err != nil {
851 t.Errorf("Marshal(Bracket): got %v, want nil error", err)
852 }
Joe Tsai71acbc72018-11-28 19:27:29 -0800853 if string(gotOut) != tt.wantOutBracket {
Joe Tsai27c2a762018-08-01 16:48:18 -0700854 t.Errorf("Marshal(Bracket):\ngot: %s\nwant: %s", gotOut, tt.wantOutBracket)
855 }
856 }
857 if tt.wantOutASCII != "" {
858 gotOut, err := Marshal(tt.wantVal, "", [2]byte{0, 0}, true)
859 if err != nil {
860 t.Errorf("Marshal(ASCII): got %v, want nil error", err)
861 }
Joe Tsai71acbc72018-11-28 19:27:29 -0800862 if string(gotOut) != tt.wantOutASCII {
Joe Tsai27c2a762018-08-01 16:48:18 -0700863 t.Errorf("Marshal(ASCII):\ngot: %s\nwant: %s", gotOut, tt.wantOutASCII)
864 }
865 }
866 if tt.wantOutIndent != "" {
867 gotOut, err := Marshal(tt.wantVal, "\t", [2]byte{0, 0}, false)
868 if err != nil {
869 t.Errorf("Marshal(Indent): got %v, want nil error", err)
870 }
Joe Tsai71acbc72018-11-28 19:27:29 -0800871 if string(gotOut) != tt.wantOutIndent {
Joe Tsai27c2a762018-08-01 16:48:18 -0700872 t.Errorf("Marshal(Indent):\ngot: %s\nwant: %s", gotOut, tt.wantOutIndent)
873 }
874 }
875 })
876 }
877}