1// Copyright 2009 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 strconv_test
6
7import (
8	. "strconv"
9	"strings"
10	"testing"
11	"unicode"
12)
13
14// Verify that our IsPrint agrees with unicode.IsPrint.
15func TestIsPrint(t *testing.T) {
16	n := 0
17	for r := rune(0); r <= unicode.MaxRune; r++ {
18		if IsPrint(r) != unicode.IsPrint(r) {
19			t.Errorf("IsPrint(%U)=%t incorrect", r, IsPrint(r))
20			n++
21			if n > 10 {
22				return
23			}
24		}
25	}
26}
27
28// Verify that our IsGraphic agrees with unicode.IsGraphic.
29func TestIsGraphic(t *testing.T) {
30	n := 0
31	for r := rune(0); r <= unicode.MaxRune; r++ {
32		if IsGraphic(r) != unicode.IsGraphic(r) {
33			t.Errorf("IsGraphic(%U)=%t incorrect", r, IsGraphic(r))
34			n++
35			if n > 10 {
36				return
37			}
38		}
39	}
40}
41
42type quoteTest struct {
43	in      string
44	out     string
45	ascii   string
46	graphic string
47}
48
49var quotetests = []quoteTest{
50	{"\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"`},
51	{"\\", `"\\"`, `"\\"`, `"\\"`},
52	{"abc\xffdef", `"abc\xffdef"`, `"abc\xffdef"`, `"abc\xffdef"`},
53	{"\u263a", `"☺"`, `"\u263a"`, `"☺"`},
54	{"\U0010ffff", `"\U0010ffff"`, `"\U0010ffff"`, `"\U0010ffff"`},
55	{"\x04", `"\x04"`, `"\x04"`, `"\x04"`},
56	// Some non-printable but graphic runes. Final column is double-quoted.
57	{"!\u00a0!\u2000!\u3000!", `"!\u00a0!\u2000!\u3000!"`, `"!\u00a0!\u2000!\u3000!"`, "\"!\u00a0!\u2000!\u3000!\""},
58	{"\x7f", `"\x7f"`, `"\x7f"`, `"\x7f"`},
59}
60
61func TestQuote(t *testing.T) {
62	for _, tt := range quotetests {
63		if out := Quote(tt.in); out != tt.out {
64			t.Errorf("Quote(%s) = %s, want %s", tt.in, out, tt.out)
65		}
66		if out := AppendQuote([]byte("abc"), tt.in); string(out) != "abc"+tt.out {
67			t.Errorf("AppendQuote(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.out)
68		}
69	}
70}
71
72func TestQuoteToASCII(t *testing.T) {
73	for _, tt := range quotetests {
74		if out := QuoteToASCII(tt.in); out != tt.ascii {
75			t.Errorf("QuoteToASCII(%s) = %s, want %s", tt.in, out, tt.ascii)
76		}
77		if out := AppendQuoteToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii {
78			t.Errorf("AppendQuoteToASCII(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.ascii)
79		}
80	}
81}
82
83func TestQuoteToGraphic(t *testing.T) {
84	for _, tt := range quotetests {
85		if out := QuoteToGraphic(tt.in); out != tt.graphic {
86			t.Errorf("QuoteToGraphic(%s) = %s, want %s", tt.in, out, tt.graphic)
87		}
88		if out := AppendQuoteToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic {
89			t.Errorf("AppendQuoteToGraphic(%q, %s) = %s, want %s", "abc", tt.in, out, "abc"+tt.graphic)
90		}
91	}
92}
93
94func BenchmarkQuote(b *testing.B) {
95	for i := 0; i < b.N; i++ {
96		Quote("\a\b\f\r\n\t\v\a\b\f\r\n\t\v\a\b\f\r\n\t\v")
97	}
98}
99
100func BenchmarkQuoteRune(b *testing.B) {
101	for i := 0; i < b.N; i++ {
102		QuoteRune('\a')
103	}
104}
105
106var benchQuoteBuf []byte
107
108func BenchmarkAppendQuote(b *testing.B) {
109	for i := 0; i < b.N; i++ {
110		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")
111	}
112}
113
114var benchQuoteRuneBuf []byte
115
116func BenchmarkAppendQuoteRune(b *testing.B) {
117	for i := 0; i < b.N; i++ {
118		benchQuoteRuneBuf = AppendQuoteRune(benchQuoteRuneBuf[:0], '\a')
119	}
120}
121
122type quoteRuneTest struct {
123	in      rune
124	out     string
125	ascii   string
126	graphic string
127}
128
129var quoterunetests = []quoteRuneTest{
130	{'a', `'a'`, `'a'`, `'a'`},
131	{'\a', `'\a'`, `'\a'`, `'\a'`},
132	{'\\', `'\\'`, `'\\'`, `'\\'`},
133	{0xFF, `'ÿ'`, `'\u00ff'`, `'ÿ'`},
134	{0x263a, `'☺'`, `'\u263a'`, `'☺'`},
135	{0xdead, `'�'`, `'\ufffd'`, `'�'`},
136	{0xfffd, `'�'`, `'\ufffd'`, `'�'`},
137	{0x0010ffff, `'\U0010ffff'`, `'\U0010ffff'`, `'\U0010ffff'`},
138	{0x0010ffff + 1, `'�'`, `'\ufffd'`, `'�'`},
139	{0x04, `'\x04'`, `'\x04'`, `'\x04'`},
140	// Some differences between graphic and printable. Note the last column is double-quoted.
141	{'\u00a0', `'\u00a0'`, `'\u00a0'`, "'\u00a0'"},
142	{'\u2000', `'\u2000'`, `'\u2000'`, "'\u2000'"},
143	{'\u3000', `'\u3000'`, `'\u3000'`, "'\u3000'"},
144}
145
146func TestQuoteRune(t *testing.T) {
147	for _, tt := range quoterunetests {
148		if out := QuoteRune(tt.in); out != tt.out {
149			t.Errorf("QuoteRune(%U) = %s, want %s", tt.in, out, tt.out)
150		}
151		if out := AppendQuoteRune([]byte("abc"), tt.in); string(out) != "abc"+tt.out {
152			t.Errorf("AppendQuoteRune(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.out)
153		}
154	}
155}
156
157func TestQuoteRuneToASCII(t *testing.T) {
158	for _, tt := range quoterunetests {
159		if out := QuoteRuneToASCII(tt.in); out != tt.ascii {
160			t.Errorf("QuoteRuneToASCII(%U) = %s, want %s", tt.in, out, tt.ascii)
161		}
162		if out := AppendQuoteRuneToASCII([]byte("abc"), tt.in); string(out) != "abc"+tt.ascii {
163			t.Errorf("AppendQuoteRuneToASCII(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.ascii)
164		}
165	}
166}
167
168func TestQuoteRuneToGraphic(t *testing.T) {
169	for _, tt := range quoterunetests {
170		if out := QuoteRuneToGraphic(tt.in); out != tt.graphic {
171			t.Errorf("QuoteRuneToGraphic(%U) = %s, want %s", tt.in, out, tt.graphic)
172		}
173		if out := AppendQuoteRuneToGraphic([]byte("abc"), tt.in); string(out) != "abc"+tt.graphic {
174			t.Errorf("AppendQuoteRuneToGraphic(%q, %U) = %s, want %s", "abc", tt.in, out, "abc"+tt.graphic)
175		}
176	}
177}
178
179type canBackquoteTest struct {
180	in  string
181	out bool
182}
183
184var canbackquotetests = []canBackquoteTest{
185	{"`", false},
186	{string(rune(0)), false},
187	{string(rune(1)), false},
188	{string(rune(2)), false},
189	{string(rune(3)), false},
190	{string(rune(4)), false},
191	{string(rune(5)), false},
192	{string(rune(6)), false},
193	{string(rune(7)), false},
194	{string(rune(8)), false},
195	{string(rune(9)), true}, // \t
196	{string(rune(10)), false},
197	{string(rune(11)), false},
198	{string(rune(12)), false},
199	{string(rune(13)), false},
200	{string(rune(14)), false},
201	{string(rune(15)), false},
202	{string(rune(16)), false},
203	{string(rune(17)), false},
204	{string(rune(18)), false},
205	{string(rune(19)), false},
206	{string(rune(20)), false},
207	{string(rune(21)), false},
208	{string(rune(22)), false},
209	{string(rune(23)), false},
210	{string(rune(24)), false},
211	{string(rune(25)), false},
212	{string(rune(26)), false},
213	{string(rune(27)), false},
214	{string(rune(28)), false},
215	{string(rune(29)), false},
216	{string(rune(30)), false},
217	{string(rune(31)), false},
218	{string(rune(0x7F)), false},
219	{`' !"#$%&'()*+,-./:;<=>?@[\]^_{|}~`, true},
220	{`0123456789`, true},
221	{`ABCDEFGHIJKLMNOPQRSTUVWXYZ`, true},
222	{`abcdefghijklmnopqrstuvwxyz`, true},
223	{`☺`, true},
224	{"\x80", false},
225	{"a\xe0\xa0z", false},
226	{"\ufeffabc", false},
227	{"a\ufeffz", false},
228}
229
230func TestCanBackquote(t *testing.T) {
231	for _, tt := range canbackquotetests {
232		if out := CanBackquote(tt.in); out != tt.out {
233			t.Errorf("CanBackquote(%q) = %v, want %v", tt.in, out, tt.out)
234		}
235	}
236}
237
238type unQuoteTest struct {
239	in  string
240	out string
241}
242
243var unquotetests = []unQuoteTest{
244	{`""`, ""},
245	{`"a"`, "a"},
246	{`"abc"`, "abc"},
247	{`"☺"`, "☺"},
248	{`"hello world"`, "hello world"},
249	{`"\xFF"`, "\xFF"},
250	{`"\377"`, "\377"},
251	{`"\u1234"`, "\u1234"},
252	{`"\U00010111"`, "\U00010111"},
253	{`"\U0001011111"`, "\U0001011111"},
254	{`"\a\b\f\n\r\t\v\\\""`, "\a\b\f\n\r\t\v\\\""},
255	{`"'"`, "'"},
256
257	{`'a'`, "a"},
258	{`'☹'`, "☹"},
259	{`'\a'`, "\a"},
260	{`'\x10'`, "\x10"},
261	{`'\377'`, "\377"},
262	{`'\u1234'`, "\u1234"},
263	{`'\U00010111'`, "\U00010111"},
264	{`'\t'`, "\t"},
265	{`' '`, " "},
266	{`'\''`, "'"},
267	{`'"'`, "\""},
268
269	{"``", ``},
270	{"`a`", `a`},
271	{"`abc`", `abc`},
272	{"`☺`", `☺`},
273	{"`hello world`", `hello world`},
274	{"`\\xFF`", `\xFF`},
275	{"`\\377`", `\377`},
276	{"`\\`", `\`},
277	{"`\n`", "\n"},
278	{"`	`", `	`},
279	{"` `", ` `},
280	{"`a\rb`", "ab"},
281}
282
283var misquoted = []string{
284	``,
285	`"`,
286	`"a`,
287	`"'`,
288	`b"`,
289	`"\"`,
290	`"\9"`,
291	`"\19"`,
292	`"\129"`,
293	`'\'`,
294	`'\9'`,
295	`'\19'`,
296	`'\129'`,
297	`'ab'`,
298	`"\x1!"`,
299	`"\U12345678"`,
300	`"\z"`,
301	"`",
302	"`xxx",
303	"``x\r",
304	"`\"",
305	`"\'"`,
306	`'\"'`,
307	"\"\n\"",
308	"\"\\n\n\"",
309	"'\n'",
310	`"\udead"`,
311	`"\ud83d\ude4f"`,
312}
313
314func TestUnquote(t *testing.T) {
315	for _, tt := range unquotetests {
316		testUnquote(t, tt.in, tt.out, nil)
317	}
318	for _, tt := range quotetests {
319		testUnquote(t, tt.out, tt.in, nil)
320	}
321	for _, s := range misquoted {
322		testUnquote(t, s, "", ErrSyntax)
323	}
324}
325
326// Issue 23685: invalid UTF-8 should not go through the fast path.
327func TestUnquoteInvalidUTF8(t *testing.T) {
328	tests := []struct {
329		in string
330
331		// one of:
332		want    string
333		wantErr error
334	}{
335		{in: `"foo"`, want: "foo"},
336		{in: `"foo`, wantErr: ErrSyntax},
337		{in: `"` + "\xc0" + `"`, want: "\xef\xbf\xbd"},
338		{in: `"a` + "\xc0" + `"`, want: "a\xef\xbf\xbd"},
339		{in: `"\t` + "\xc0" + `"`, want: "\t\xef\xbf\xbd"},
340	}
341	for _, tt := range tests {
342		testUnquote(t, tt.in, tt.want, tt.wantErr)
343	}
344}
345
346func testUnquote(t *testing.T, in, want string, wantErr error) {
347	// Test Unquote.
348	got, gotErr := Unquote(in)
349	if got != want || gotErr != wantErr {
350		t.Errorf("Unquote(%q) = (%q, %v), want (%q, %v)", in, got, gotErr, want, wantErr)
351	}
352
353	// Test QuotedPrefix.
354	// Adding an arbitrary suffix should not change the result of QuotedPrefix
355	// assume that the suffix doesn't accidentally terminate a truncated input.
356	if gotErr == nil {
357		want = in
358	}
359	suffix := "\n\r\\\"`'" // special characters for quoted strings
360	if len(in) > 0 {
361		suffix = strings.ReplaceAll(suffix, in[:1], "")
362	}
363	in += suffix
364	got, gotErr = QuotedPrefix(in)
365	if gotErr == nil && wantErr != nil {
366		_, wantErr = Unquote(got) // original input had trailing junk, reparse with only valid prefix
367		want = got
368	}
369	if got != want || gotErr != wantErr {
370		t.Errorf("QuotedPrefix(%q) = (%q, %v), want (%q, %v)", in, got, gotErr, want, wantErr)
371	}
372}
373
374func BenchmarkUnquoteEasy(b *testing.B) {
375	for i := 0; i < b.N; i++ {
376		Unquote(`"Give me a rock, paper and scissors and I will move the world."`)
377	}
378}
379
380func BenchmarkUnquoteHard(b *testing.B) {
381	for i := 0; i < b.N; i++ {
382		Unquote(`"\x47ive me a \x72ock, \x70aper and \x73cissors and \x49 will move the world."`)
383	}
384}
385