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