Files
devangpratap c7226d702e Do not drop an escaped U+FFFD in the shlex lexer
write_escaped_ch compares the decoded rune against utf8.RuneError to
detect invalid UTF-8, but a literal U+FFFD decodes to that same rune, so
an escaped replacement character was silently deleted. The size returned
by DecodeRuneInString already tells the two apart: invalid bytes decode
with a size of one, a real U+FFFD with a size of three. Keep the rune
when the size is greater than one.
2026-09-21 12:05:20 -04:00

146 lines
3.9 KiB
Go

package shlex
import (
"testing"
"github.com/google/go-cmp/cmp"
)
var (
// one two "three four" "five \"six\"" seven#eight # nine # ten
// eleven 'twelve\'
testString = "one two \"three four\" \"five \\\"six\\\"\" seven#eight # nine # ten eleven 'twelve\\' thirteen=13 fourteen/14"
)
func TestLexer(t *testing.T) {
testInput := testString
expectedStrings := []string{"one", "two", "three four", "five \"six\"", "seven#eight", "#", "nine", "#", "ten", "eleven", "twelve\\", "thirteen=13", "fourteen/14"}
lexer := NewLexer(testInput)
for i, want := range expectedStrings {
got := lexer.Next()
if got.Value != want {
t.Errorf("Lexer.Next()[%v] of %q -> %v. Want: %v", i, testString, got, want)
}
}
}
type Tok struct {
Pos int
Val string
}
func TestSplit(t *testing.T) {
want := []string{"one", "two", "three four", "five \"six\"", "seven#eight", "#", "nine", "#", "ten", "eleven", "twelve\\", "thirteen=13", "fourteen/14"}
got, err := Split(testString)
if err != nil {
t.Error(err)
}
if len(want) != len(got) {
t.Errorf("Split(%q) -> %v. Want: %v", testString, got, want)
}
for i := range got {
if got[i] != want[i] {
t.Errorf("Split(%q)[%v] -> %v. Want: %v", testString, i, got[i], want[i])
}
}
for _, x := range []string{
`abc\`, `\`, `'abc`, `'`, `"`, `asd\`,
} {
_, err := Split(x)
if err == nil {
t.Fatalf("Failed to get an error for: %#v", x)
}
}
s := func(q string) (ans []Tok) {
l := NewLexer(q)
for {
w := l.Next()
if w.Err != nil {
t.Fatal(w.Err)
}
if w.Pos < 0 {
break
}
ans = append(ans, Tok{w.Pos, w.Value})
}
return
}
for q, expected := range map[string][]Tok{
`a""`: {{0, "a"}},
`a""b`: {{0, "ab"}},
`-1 "" 2`: {{0, "-1"}, {3, ""}, {6, "2"}},
`-1 '' 2`: {{0, "-1"}, {3, ""}, {6, "2"}},
`a ""`: {{0, "a"}, {2, ""}},
`""`: {{0, ""}},
`"ab"`: {{0, "ab"}},
`x "ab"y \m`: {{0, `x`}, {2, `aby`}, {8, `m`}},
`x'y"\z'1`: {{0, `xy"\z1`}},
`\abc\ d`: {{0, `abc d`}},
``: nil,
` `: nil,
" \tabc\n\t\r ": {{2, "abc"}},
"a\\\xffb": {{0, "ab"}}, // an escaped invalid byte is still dropped
// An escaped U+FFFD is a real character, not a decoding failure, so it
// survives the escape the way it does unescaped or single quoted.
"a\\�b": {{0, "a�b"}},
"\"a\\�b\"": {{0, "a�b"}},
} {
if diff := cmp.Diff(expected, s(q)); diff != "" {
t.Fatalf("Failed for string: %#v\n%s", q, diff)
}
}
}
func TestSplitForCompletion(t *testing.T) {
test := func(cmdline string, last_arg_pos int, expected ...string) {
actual, actual_pos := SplitForCompletion(cmdline)
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("Failed to split: %s\n%s", cmdline, diff)
}
if last_arg_pos != actual_pos {
t.Fatalf("Failed to split: %#v\n Last arg pos: %d != %d", cmdline, last_arg_pos, actual_pos)
}
}
test("a b", 2, "a", "b")
test("a b ", 4, "a", "b", "")
test("a b ", 5, "a", "b", "")
test("ab cd ", 6, "ab", "cd", "")
test("launch --type ", 14, "launch", "--type", "")
test(`a "b c" `, 8, "a", "b c", "")
test(`a "b c"`, 2, "a", "b c")
test(`a "b c`, 2, "a", "b c")
test(`a "" b`, 5, "a", "", "b")
test(`a '' `, 5, "a", "", "")
}
func TestExpandANSICEscapes(t *testing.T) {
var m = map[string]string{
"abc": "abc",
`a\ab`: "a\ab",
`a\eb`: "a\x1bb",
`a\r\nb`: "a\r\nb",
`a\c b`: "a\000b",
`a\c`: "a\\c",
`a\x1bb`: "a\x1bb",
`a\x1b`: "a\x1b",
`a\x1`: "a\x01",
`a\x1\\`: "a\x01\\",
`a\x1g`: "a\x01g",
`a\z\"`: "a\\z\"",
`a\123b`: "a\123b",
`a\128b`: "a\0128b",
`a\u1234e`: "a\u1234e",
`a\U1f1eez`: "a\U0001f1eez",
}
for q, expected := range m {
actual := ExpandANSICEscapes(q)
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("Failed to process: %#v\n%s", q, diff)
}
}
}