1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
|
package shell
import (
"fmt"
"io"
"log"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestQuote(t *testing.T) {
type testCase struct{ in, want string }
tests := []testCase{
{"", "''"}, // empty is special
{"abc", "abc"}, // nothing to quote
{"--flag", "--flag"}, // "
{"'abc", `\'abc`}, // single quote only
{"abc'", `abc\'`}, // "
{`shan't`, `shan\'t`}, // "
{"--flag=value", `'--flag=value'`},
{"a b\tc", "'a b\tc'"},
{`a"b"c`, `'a"b"c'`},
{`'''`, `\'\'\'`},
{`\`, `'\'`},
{`'a=b`, `\''a=b'`}, // quotes and other stuff
{`a='b`, `'a='\''b'`}, // "
{`a=b'`, `'a=b'\'`}, // "
}
// Verify that all the designated special characters get quoted.
for _, c := range shouldQuote + mustQuote {
tests = append(tests, testCase{
in: string(c),
want: fmt.Sprintf(`'%c'`, c),
})
}
for _, test := range tests {
got := Quote(test.in)
if got != test.want {
t.Errorf("Quote %q: got %q, want %q", test.in, got, test.want)
}
}
}
func TestSplit(t *testing.T) {
tests := []struct {
in string
want []string
ok bool
}{
// Variations of empty input yield an empty split.
{"", nil, true},
{" ", nil, true},
{"\t", nil, true},
{"\n ", nil, true},
// Various escape sequences work properly.
{`\ `, []string{" "}, true},
{`a\ `, []string{"a "}, true},
{`\\a`, []string{`\a`}, true},
{`"a\"b"`, []string{`a"b`}, true},
{`'\'`, []string{"\\"}, true},
// Leading and trailing whitespace are discarded correctly.
{"a", []string{"a"}, true},
{" a", []string{"a"}, true},
{"a\n", []string{"a"}, true},
// Escaped newlines are magic in the correct ways.
{"a\\\nb", []string{"ab"}, true},
{"a \\\n b\tc", []string{"a", "b", "c"}, true},
// Various splits with and without quotes. Quoted whitespace is
// preserved.
{"a b c", []string{"a", "b", "c"}, true},
{`a 'b c'`, []string{"a", "b c"}, true},
{"\"a\nb\"cd e'f'", []string{"a\nbcd", "ef"}, true},
{"'\n \t '", []string{"\n \t "}, true},
// Quoted empty strings are preserved in various places.
{"''", []string{""}, true},
{"a ''", []string{"a", ""}, true},
{" a \"\" b ", []string{"a", "", "b"}, true},
{"'' a", []string{"", "a"}, true},
// Unbalanced quotation marks and escapes are detected.
{"\\", []string{""}, false}, // escape without a target
{"'", []string{""}, false}, // unclosed single
{`"`, []string{""}, false}, // unclosed double
{`'\''`, []string{`\`}, false}, // unclosed connected double
{`"\\" '`, []string{`\`, ``}, false}, // unclosed separate single
{"a 'b c", []string{"a", "b c"}, false},
{`a "b c`, []string{"a", "b c"}, false},
{`a "b \"`, []string{"a", `b "`}, false},
}
for _, test := range tests {
got, ok := Split(test.in)
if ok != test.ok {
t.Errorf("Split %#q: got valid=%v, want %v", test.in, ok, test.ok)
}
if diff := cmp.Diff(test.want, got); diff != "" {
t.Errorf("Split %#q: (-want, +got)\n%s", test.in, diff)
}
}
}
func TestScannerSplit(t *testing.T) {
tests := []struct {
in string
want, rest []string
}{
{"", nil, nil},
{" ", nil, nil},
{"--", nil, nil},
{"a -- b", []string{"a"}, []string{"b"}},
{"a b c -- d -- e ", []string{"a", "b", "c"}, []string{"d", "--", "e"}},
{`"a b c --" -- "d "`, []string{"a b c --"}, []string{"d "}},
{` -- "foo`, nil, []string{"foo"}}, // unterminated
{"cmd -flag -- arg1 arg2", []string{"cmd", "-flag"}, []string{"arg1", "arg2"}},
}
for _, test := range tests {
t.Logf("Scanner split input: %q", test.in)
s := NewScanner(strings.NewReader(test.in))
var got, rest []string
for s.Next() {
if s.Text() == "--" {
rest = s.Split()
break
}
got = append(got, s.Text())
}
if s.Err() != io.EOF {
t.Errorf("Unexpected scan error: %v", s.Err())
}
if diff := cmp.Diff(test.want, got); diff != "" {
t.Errorf("Scanner split prefix: (-want, +got)\n%s", diff)
}
if diff := cmp.Diff(test.rest, rest); diff != "" {
t.Errorf("Scanner split suffix: (-want, +got)\n%s", diff)
}
}
}
func TestRoundTrip(t *testing.T) {
tests := [][]string{
nil,
{"a"},
{"a "},
{"a", "b", "c"},
{"a", "b c"},
{"--flag=value"},
{"m='$USER'", "nop+", "$$"},
{`"a" b `, "c"},
{"odd's", "bodkins", "x'", "x''", "x\"\"", "$x':y"},
{"a=b", "--foo", "${bar}", `\$`},
{"cat", "a${b}.txt", "|", "tee", "capture", "2>", "/dev/null"},
}
for _, test := range tests {
s := Join(test)
t.Logf("Join %#q = %v", test, s)
got, ok := Split(s)
if !ok {
t.Errorf("Split %+q: should be valid, but is not", s)
}
if diff := cmp.Diff(test, got); diff != "" {
t.Errorf("Split %+q: (-want, +got)\n%s", s, diff)
}
}
}
func ExampleScanner() {
const input = `a "free range" exploration of soi\ disant novelties`
s := NewScanner(strings.NewReader(input))
sum, count := 0, 0
for s.Next() {
count++
sum += len(s.Text())
}
fmt.Println(len(input), count, sum, s.Complete(), s.Err())
// Output: 51 6 43 true EOF
}
func ExampleScanner_Rest() {
const input = `things 'and stuff' %end% all the remaining stuff`
s := NewScanner(strings.NewReader(input))
for s.Next() {
if s.Text() == "%end%" {
fmt.Print("found marker; ")
break
}
}
rest, err := io.ReadAll(s.Rest())
if err != nil {
log.Fatal(err)
}
fmt.Println(string(rest))
// Output: found marker; all the remaining stuff
}
func ExampleScanner_Each() {
const input = `a\ b 'c d' "e f's g" stop "go directly to jail"`
if err := NewScanner(strings.NewReader(input)).Each(func(tok string) bool {
fmt.Println(tok)
return tok != "stop"
}); err != nil {
log.Fatal(err)
}
// Output:
// a b
// c d
// e f's g
// stop
}
func ExampleScanner_Split() {
const input = `cmd -flag=t -- foo bar baz`
s := NewScanner(strings.NewReader(input))
for s.Next() {
if s.Text() == "--" {
fmt.Println("** Args:", strings.Join(s.Split(), ", "))
} else {
fmt.Println(s.Text())
}
}
// Output:
// cmd
// -flag=t
// ** Args: foo, bar, baz
}
|