// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package strconv_test import ( . "strconv" "strings" "testing" "unicode" ) // Verify that our IsPrint agrees with unicode.IsPrint. func TestIsPrint(t *testing.T) { n := 0 for r := rune(0); r <= unicode.MaxRune; r++ { if IsPrint(r) != unicode.IsPrint(r) { t.Errorf("IsPrint(%U)=%t incorrect", r, IsPrint(r)) n++ if n > 10 { return } } } } // Verify that our IsGraphic agrees with unicode.IsGraphic. func TestIsGraphic(t *testing.T) { n := 0 for r := rune(0); r <= unicode.MaxRune; r++ { if IsGraphic(r) != unicode.IsGraphic(r) { t.Errorf("IsGraphic(%U)=%t incorrect", r, IsGraphic(r)) n++ if n > 10 { return } } } } type quoteTest struct { in string out string ascii string graphic string } var quotetests = []quoteTest{ {"\a\b\f\r\n\t\v", `"\a\b\f\r\n\t\v"`, `"\a\b\f\r\n\t\v"`, `"\a\b\f\r\n\t\v"`}, {"\\", `"\\"`, `"\\"`, `"\\"`}, {"abc\xffdef", `"abc\xffdef"`, `"abc\xffdef"`, `"abc\xffdef"`}, {"\u263a", `"☺"`, `"\u263a"`, `"☺"`}, {"\U0010ffff", `"\U0010ffff"`, `"\U0010ffff"`, `"\U0010ffff"`}, {"\x04", `"\x04"`, `"\x04"`, `"\x04"`}, // Some non-printable but graphic runes. Final column is double-quoted. {"!\u00a0!\u2000!\u3000!", `"!\u00a0!\u2000!\u3000!"`, `"!\u00a0!\u2000!\u3000!"`, "\"!\u00a0!\u2000!\u3000!\""}, {"\x7f", `"\x7f"`, `"\x7f"`, `"\x7f"`}, } func TestQuote(t *testing.T) { for _, tt := range quotetests { if out := Quote(tt.in); out != tt.out { t.Errorf("Quote(%s) = %s, want %s", tt.in, out, tt.out) } if out := AppendQuote([]byte("abc"), tt.in); string(out) != "abc"+tt.out { t.Errorf("AppendQuote(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.out) } } } func TestQuoteToASCII(t *testing.T) { for _, tt := range quotetests { if out := QuoteToASCII(tt.in); out != tt.ascii { t.Errorf("QuoteToASCII(%s) = %s, want %s", tt.in, out, tt.ascii) } if out := AppendQuoteToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { t.Errorf("AppendQuoteToASCII(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.ascii) } } } func TestQuoteToGraphic(t *testing.T) { for _, tt := range quotetests { if out := QuoteToGraphic(tt.in); out != tt.graphic { t.Errorf("QuoteToGraphic(%s) = %s, want %s", tt.in, out, tt.graphic) } if out := AppendQuoteToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { t.Errorf("AppendQuoteToGraphic(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.graphic) } } } func BenchmarkQuote(b *testing.B) { for i := 0; i < b.N; i++ { Quote("\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") } } func BenchmarkQuoteRune(b *testing.B) { for i := 0; i < b.N; i++ { QuoteRune('\a') } } var benchQuoteBuf []byte func BenchmarkAppendQuote(b *testing.B) { for i := 0; i < b.N; i++ { benchQuoteBuf = AppendQuote(benchQuoteBuf[:0], "\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v") } } var benchQuoteRuneBuf []byte func BenchmarkAppendQuoteRune(b *testing.B) { for i := 0; i < b.N; i++ { benchQuoteRuneBuf = AppendQuoteRune(benchQuoteRuneBuf[:0], '\a') } } type quoteRuneTest struct { in rune out string ascii string graphic string } var quoterunetests = []quoteRuneTest{ {'a', `'a'`, `'a'`, `'a'`}, {'\a', `'\a'`, `'\a'`, `'\a'`}, {'\\', `'\\'`, `'\\'`, `'\\'`}, {0xFF, `'ÿ'`, `'\u00ff'`, `'ÿ'`}, {0x263a, `'☺'`, `'\u263a'`, `'☺'`}, {0xdead, `'�'`, `'\ufffd'`, `'�'`}, {0xfffd, `'�'`, `'\ufffd'`, `'�'`}, {0x0010ffff, `'\U0010ffff'`, `'\U0010ffff'`, `'\U0010ffff'`}, {0x0010ffff + 1, `'�'`, `'\ufffd'`, `'�'`}, {0x04, `'\x04'`, `'\x04'`, `'\x04'`}, // Some differences between graphic and printable. Note the last column is double-quoted. {'\u00a0', `'\u00a0'`, `'\u00a0'`, "'\u00a0'"}, {'\u2000', `'\u2000'`, `'\u2000'`, "'\u2000'"}, {'\u3000', `'\u3000'`, `'\u3000'`, "'\u3000'"}, } func TestQuoteRune(t *testing.T) { for _, tt := range quoterunetests { if out := QuoteRune(tt.in); out != tt.out { t.Errorf("QuoteRune(%U) = %s, want %s", tt.in, out, tt.out) } if out := AppendQuoteRune([]byte("abc"), tt.in); string(out) != "abc"+tt.out { t.Errorf("AppendQuoteRune(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.out) } } } func TestQuoteRuneToASCII(t *testing.T) { for _, tt := range quoterunetests { if out := QuoteRuneToASCII(tt.in); out != tt.ascii { t.Errorf("QuoteRuneToASCII(%U) = %s, want %s", tt.in, out, tt.ascii) } if out := AppendQuoteRuneToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii { t.Errorf("AppendQuoteRuneToASCII(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.ascii) } } } func TestQuoteRuneToGraphic(t *testing.T) { for _, tt := range quoterunetests { if out := QuoteRuneToGraphic(tt.in); out != tt.graphic { t.Errorf("QuoteRuneToGraphic(%U) = %s, want %s", tt.in, out, tt.graphic) } if out := AppendQuoteRuneToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic { t.Errorf("AppendQuoteRuneToGraphic(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.graphic) } } } type canBackquoteTest struct { in string out bool } var canbackquotetests = []canBackquoteTest{ {"`", false}, {string(rune(0)), false}, {string(rune(1)), false}, {string(rune(2)), false}, {string(rune(3)), false}, {string(rune(4)), false}, {string(rune(5)), false}, {string(rune(6)), false}, {string(rune(7)), false}, {string(rune(8)), false}, {string(rune(9)), true}, // \t {string(rune(10)), false}, {string(rune(11)), false}, {string(rune(12)), false}, {string(rune(13)), false}, {string(rune(14)), false}, {string(rune(15)), false}, {string(rune(16)), false}, {string(rune(17)), false}, {string(rune(18)), false}, {string(rune(19)), false}, {string(rune(20)), false}, {string(rune(21)), false}, {string(rune(22)), false}, {string(rune(23)), false}, {string(rune(24)), false}, {string(rune(25)), false}, {string(rune(26)), false}, {string(rune(27)), false}, {string(rune(28)), false}, {string(rune(29)), false}, {string(rune(30)), false}, {string(rune(31)), false}, {string(rune(0x7F)), false}, {`' !"#$%&'()*+,-./:;<=>?@[\]^_{|}~`, true}, {`0123456789`, true}, {`ABCDEFGHIJKLMNOPQRSTUVWXYZ`, true}, {`abcdefghijklmnopqrstuvwxyz`, true}, {`☺`, true}, {"\x80", false}, {"a\xe0\xa0z", false}, {"\ufeffabc", false}, {"a\ufeffz", false}, } func TestCanBackquote(t *testing.T) { for _, tt := range canbackquotetests { if out := CanBackquote(tt.in); out != tt.out { t.Errorf("CanBackquote(%q) = %v, want %v", tt.in, out, tt.out) } } } type unQuoteTest struct { in string out string } var unquotetests = []unQuoteTest{ {`""`, ""}, {`"a"`, "a"}, {`"abc"`, "abc"}, {`"☺"`, "☺"}, {`"hello world"`, "hello world"}, {`"\xFF"`, "\xFF"}, {`"\377"`, "\377"}, {`"\u1234"`, "\u1234"}, {`"\U00010111"`, "\U00010111"}, {`"\U0001011111"`, "\U0001011111"}, {`"\a\b\f\n\r\t\v\\\""`, "\a\b\f\n\r\t\v\\\""}, {`"'"`, "'"}, {`'a'`, "a"}, {`'☹'`, "☹"}, {`'\a'`, "\a"}, {`'\x10'`, "\x10"}, {`'\377'`, "\377"}, {`'\u1234'`, "\u1234"}, {`'\U00010111'`, "\U00010111"}, {`'\t'`, "\t"}, {`' '`, " "}, {`'\''`, "'"}, {`'"'`, "\""}, {"``", ``}, {"`a`", `a`}, {"`abc`", `abc`}, {"`☺`", `☺`}, {"`hello world`", `hello world`}, {"`\\xFF`", `\xFF`}, {"`\\377`", `\377`}, {"`\\`", `\`}, {"`\n`", "\n"}, {"` `", ` `}, {"` `", ` `}, {"`a\rb`", "ab"}, } var misquoted = []string{ ``, `"`, `"a`, `"'`, `b"`, `"\"`, `"\9"`, `"\19"`, `"\129"`, `'\'`, `'\9'`, `'\19'`, `'\129'`, `'ab'`, `"\x1!"`, `"\U12345678"`, `"\z"`, "`", "`xxx", "``x\r", "`\"", `"\'"`, `'\"'`, "\"\n\"", "\"\\n\n\"", "'\n'", `"\udead"`, `"\ud83d\ude4f"`, } func TestUnquote(t *testing.T) { for _, tt := range unquotetests { testUnquote(t, tt.in, tt.out, nil) } for _, tt := range quotetests { testUnquote(t, tt.out, tt.in, nil) } for _, s := range misquoted { testUnquote(t, s, "", ErrSyntax) } } // Issue 23685: invalid UTF-8 should not go through the fast path. func TestUnquoteInvalidUTF8(t *testing.T) { tests := []struct { in string // one of: want string wantErr error }{ {in: `"foo"`, want: "foo"}, {in: `"foo`, wantErr: ErrSyntax}, {in: `"` + "\xc0" + `"`, want: "\xef\xbf\xbd"}, {in: `"a` + "\xc0" + `"`, want: "a\xef\xbf\xbd"}, {in: `"\t` + "\xc0" + `"`, want: "\t\xef\xbf\xbd"}, } for _, tt := range tests { testUnquote(t, tt.in, tt.want, tt.wantErr) } } func testUnquote(t *testing.T, in, want string, wantErr error) { // Test Unquote. got, gotErr := Unquote(in) if got != want || gotErr != wantErr { t.Errorf("Unquote(%q) = (%q, %v), want (%q, %v)", in, got, gotErr, want, wantErr) } // Test QuotedPrefix. // Adding an arbitrary suffix should not change the result of QuotedPrefix // assume that the suffix doesn't accidentally terminate a truncated input. if gotErr == nil { want = in } suffix := "\n\r\\\"`'" // special characters for quoted strings if len(in) > 0 { suffix = strings.ReplaceAll(suffix, in[:1], "") } in += suffix got, gotErr = QuotedPrefix(in) if gotErr == nil && wantErr != nil { _, wantErr = Unquote(got) // original input had trailing junk, reparse with only valid prefix want = got } if got != want || gotErr != wantErr { t.Errorf("QuotedPrefix(%q) = (%q, %v), want (%q, %v)", in, got, gotErr, want, wantErr) } } func BenchmarkUnquoteEasy(b *testing.B) { for i := 0; i < b.N; i++ { Unquote(`"Give me a rock, paper and scissors and I will move the world."`) } } func BenchmarkUnquoteHard(b *testing.B) { for i := 0; i < b.N; i++ { Unquote(`"\x47ive me a \x72ock, \x70aper and \x73cissors and \x49 will move the world."`) } }