github.com/hattya/go.sh@v0.0.0-20240328132134-f53276d95cc6/interp/expand_test.go (about) 1 // 2 // go.sh/interp :: expand_test.go 3 // 4 // Copyright (c) 2021 Akinori Hattori <hattya@gmail.com> 5 // 6 // SPDX-License-Identifier: MIT 7 // 8 9 package interp_test 10 11 import ( 12 "fmt" 13 "os" 14 "os/user" 15 "path/filepath" 16 "reflect" 17 "runtime" 18 "strconv" 19 "strings" 20 "testing" 21 22 "github.com/hattya/go.sh/ast" 23 "github.com/hattya/go.sh/interp" 24 "github.com/hattya/go.sh/printer" 25 ) 26 27 const ( 28 sep = string(os.PathListSeparator) 29 V = "value" 30 E = "" 31 P = "foo/bar/baz" 32 ) 33 34 var expandTests = []struct { 35 word ast.Word 36 mode interp.ExpMode 37 fields []string 38 }{ 39 {word(), 0, nil}, 40 {word(), interp.Quote, []string{""}}, 41 {word(quote(`'`, word())), 0, []string{""}}, 42 {word(quote(`"`, word())), 0, []string{""}}, 43 44 {word(lit("foo")), 0, []string{"foo"}}, 45 {word(quote(`\`, word(lit("f"))), lit("oo")), 0, []string{"foo"}}, 46 {word(quote(`'`, word(lit("foo")))), 0, []string{"foo"}}, 47 {word(quote(`"`, word(lit("foo")))), 0, []string{"foo"}}, 48 {word(lit("foo"), lit("bar")), 0, []string{"foobar"}}, 49 50 {word(quote(`\`, word(lit("*")))), 0, []string{`*`}}, 51 {word(quote(`'`, word(lit("*")))), 0, []string{`*`}}, 52 {word(quote(`"`, word(lit("*")))), 0, []string{`*`}}, 53 54 {word(quote(`\`, word(lit("*")))), interp.Literal, []string{`*`}}, 55 {word(quote(`'`, word(lit("*")))), interp.Literal, []string{`*`}}, 56 {word(quote(`"`, word(lit("*")))), interp.Literal, []string{`*`}}, 57 58 {word(quote(`\`, word(lit("*")))), interp.Pattern, []string{`\*`}}, 59 {word(quote(`'`, word(lit("*")))), interp.Pattern, []string{`\*`}}, 60 {word(quote(`"`, word(lit("*")))), interp.Pattern, []string{`\*`}}, 61 62 {word(paramExp(lit("E"), "", nil), lit(" * 1")), interp.Arith, []string{"E * 1"}}, 63 {word(quote(`"`, word(paramExp(lit("E"), "", nil))), lit(" * 1")), interp.Arith, []string{"E * 1"}}, 64 } 65 66 var tildeExpTests = []struct { 67 word ast.Word 68 mode interp.ExpMode 69 fields []string 70 }{ 71 {word(lit("~")), 0, []string{homeDir()}}, 72 {word(lit("~/")), 0, []string{homeDir() + "/"}}, 73 {word(lit("~"), lit("/")), 0, []string{homeDir() + "/"}}, 74 {word(lit("~"), quote(`\`, word(lit(`/`)))), 0, []string{"~/"}}, 75 {word(lit("~"), quote(`\`, word(lit(`\`)))), 0, []string{homeDir() + `\`}}, 76 77 {word(lit("~" + username())), 0, []string{homeDir()}}, 78 {word(lit("~" + username() + "/")), 0, []string{homeDir() + "/"}}, 79 {word(lit("~"), lit(username()), lit("/")), 0, []string{homeDir() + "/"}}, 80 {word(lit("~"+username()), quote(`\`, word(lit(`/`)))), 0, []string{"~" + username() + "/"}}, 81 {word(lit("~"+username()), quote(`\`, word(lit(`\`)))), 0, []string{homeDir() + `\`}}, 82 83 {word(lit("~_")), 0, []string{"~_"}}, 84 {word(lit("~_/")), 0, []string{"~_/"}}, 85 {word(lit("~"), lit("_"), lit("/")), 0, []string{"~_/"}}, 86 {word(lit("~_"), quote(`\`, word(lit("/")))), 0, []string{"~_/"}}, 87 {word(lit("~_"), quote(`\`, word(lit(`\`)))), 0, []string{`~_\`}}, 88 89 {word(lit(sep)), interp.Assign, []string{sep}}, 90 {word(litf("~/foo%v~/bar", sep)), interp.Assign, []string{fmt.Sprintf("%v/foo%v%[1]v/bar", homeDir(), sep)}}, 91 {word(lit("~/foo"), lit(sep), lit("~/bar")), interp.Assign, []string{fmt.Sprintf("%v/foo%v%[1]v/bar", homeDir(), sep)}}, 92 {word(lit("~"), lit("/foo"), lit(sep), lit("~"), lit("/bar")), interp.Assign, []string{fmt.Sprintf("%v/foo%v%[1]v/bar", homeDir(), sep)}}, 93 {word(lit("~"), quote(`\`, word(lit(`/`))), litf("foo%v~", sep), quote(`\`, word(lit(`/`))), lit("bar")), interp.Assign, []string{fmt.Sprintf("~/foo%v~/bar", sep)}}, 94 {word(lit("~"), quote(`\`, word(lit(`\`))), litf("foo%v~", sep), quote(`\`, word(lit(`/`))), lit("bar")), interp.Assign, []string{fmt.Sprintf(`%v\foo%v~/bar`, homeDir(), sep)}}, 95 {word(lit("~"), quote(`\`, word(lit(`/`))), litf("foo%v~", sep), quote(`\`, word(lit(`\`))), lit("bar")), interp.Assign, []string{fmt.Sprintf(`~/foo%v%v\bar`, sep, homeDir())}}, 96 {word(lit("~"), quote(`\`, word(lit(`\`))), litf("foo%v~", sep), quote(`\`, word(lit(`\`))), lit("bar")), interp.Assign, []string{fmt.Sprintf(`%v\foo%v%[1]v\bar`, homeDir(), sep)}}, 97 98 {word(lit("~"), paramExp(lit("_"), "", nil), lit("/")), 0, []string{"~/"}}, 99 {word(lit("~/")), interp.Quote, []string{"~/"}}, 100 {word(quote(`"`, word(lit("~/")))), 0, []string{"~/"}}, 101 102 {word(paramExp(lit("_"), ":-", word(litf("~/foo%v~/bar", sep)))), interp.Assign, []string{fmt.Sprintf("%v/foo%v%[1]v/bar", homeDir(), sep)}}, 103 {word(paramExp(lit("E"), "+", word(litf("~/foo%v~/bar", sep)))), interp.Assign, []string{fmt.Sprintf("%v/foo%v%[1]v/bar", homeDir(), sep)}}, 104 } 105 106 var paramExpTests = []struct { 107 word ast.Word 108 fields []string 109 err string 110 assign bool 111 }{ 112 // simplest form 113 {word(paramExp(lit("V"), "", nil)), []string{V}, "", false}, 114 // use default values 115 {word(paramExp(lit("V"), ":-", word(lit("...")))), []string{V}, "", false}, 116 {word(paramExp(lit("V"), "-", word(lit("...")))), []string{V}, "", false}, 117 {word(paramExp(lit("E"), ":-", word(lit("...")))), []string{"..."}, "", false}, 118 {word(paramExp(lit("E"), "-", word(lit("...")))), []string{""}, "", false}, 119 {word(paramExp(lit("E"), ":-", word())), []string{""}, "", false}, 120 {word(paramExp(lit("_"), ":-", word(quote(`'`, word(lit("...")))))), []string{"..."}, "", false}, 121 {word(paramExp(lit("_"), "-", word(quote(`'`, word(lit("...")))))), []string{"..."}, "", false}, 122 {word(paramExp(lit("_"), ":-", word())), []string{""}, "", false}, 123 {word(paramExp(lit("_"), "-", word())), []string{""}, "", false}, 124 125 {word(paramExp(lit("_"), ":-", word(quote(`"`, word(paramExp(lit("1"), ":=", word(lit("...")))))))), nil, "$1: cannot assign ", false}, 126 {word(paramExp(lit("_"), "-", word(quote(`"`, word(paramExp(lit("1"), "=", word(lit("...")))))))), nil, "$1: cannot assign ", false}, 127 // assign default values 128 {word(paramExp(lit("V"), ":=", word(lit("...")))), []string{V}, "", false}, 129 {word(paramExp(lit("V"), "=", word(lit("...")))), []string{V}, "", false}, 130 {word(paramExp(lit("E"), ":=", word(lit("...")))), []string{"..."}, "", true}, 131 {word(paramExp(lit("E"), "=", word(lit("...")))), []string{""}, "", false}, 132 {word(paramExp(lit("E"), ":=", word())), []string{""}, "", true}, 133 {word(paramExp(lit("_"), ":=", word(lit("...")))), []string{"..."}, "", true}, 134 {word(paramExp(lit("_"), "=", word(lit("...")))), []string{"..."}, "", true}, 135 {word(paramExp(lit("_"), ":=", word())), []string{""}, "", true}, 136 {word(paramExp(lit("_"), "=", word())), []string{""}, "", true}, 137 138 {word(paramExp(lit("1"), ":=", word(lit("...")))), nil, "$1: cannot assign ", false}, 139 {word(paramExp(lit("1"), "=", word(lit("...")))), nil, "$1: cannot assign ", false}, 140 {word(paramExp(lit("@"), ":=", word(lit("...")))), nil, "$@: cannot assign ", false}, 141 {word(paramExp(lit("_"), ":=", word(quote(`"`, word(paramExp(lit("1"), ":=", word(lit("...")))))))), nil, "$1: cannot assign ", false}, 142 {word(paramExp(lit("_"), "=", word(quote(`"`, word(paramExp(lit("1"), "=", word(lit("...")))))))), nil, "$1: cannot assign ", false}, 143 // indicate error if unset or null 144 {word(paramExp(lit("V"), ":?", word(lit("...")))), []string{V}, "", false}, 145 {word(paramExp(lit("V"), "?", word(lit("...")))), []string{V}, "", false}, 146 {word(paramExp(lit("E"), ":?", word(lit("...")))), nil, "$E: ...", false}, 147 {word(paramExp(lit("E"), "?", word(lit("...")))), []string{""}, "", false}, 148 {word(paramExp(lit("_"), ":?", word(quote(`'`, word(lit("...")))))), nil, "$_: ...", false}, 149 {word(paramExp(lit("_"), "?", word(quote(`'`, word(lit("...")))))), nil, "$_: ...", false}, 150 {word(paramExp(lit("_"), ":?", word())), nil, "$_: parameter is unset or null", false}, 151 {word(paramExp(lit("_"), "?", word())), nil, "$_: parameter is unset or null", false}, 152 153 {word(paramExp(lit("_"), ":?", word(quote(`"`, word(paramExp(lit("1"), ":=", word(lit("...")))))))), nil, "$1: cannot assign ", false}, 154 {word(paramExp(lit("_"), "?", word(quote(`"`, word(paramExp(lit("1"), "=", word(lit("...")))))))), nil, "$1: cannot assign ", false}, 155 // use alternative values 156 {word(paramExp(lit("V"), ":+", word(lit("...")))), []string{"..."}, "", false}, 157 {word(paramExp(lit("V"), "+", word(lit("...")))), []string{"..."}, "", false}, 158 {word(paramExp(lit("E"), ":+", word(lit("...")))), []string{""}, "", false}, 159 {word(paramExp(lit("E"), "+", word(lit("...")))), []string{"..."}, "", false}, 160 {word(paramExp(lit("E"), "+", word())), []string{""}, "", false}, 161 {word(paramExp(lit("_"), ":+", word(lit("...")))), []string{""}, "", false}, 162 {word(paramExp(lit("_"), "+", word(lit("...")))), []string{""}, "", false}, 163 164 {word(paramExp(lit("V"), ":+", word(quote(`"`, word(paramExp(lit("1"), ":=", word(lit("...")))))))), nil, "$1: cannot assign ", false}, 165 {word(paramExp(lit("V"), "+", word(quote(`"`, word(paramExp(lit("1"), "=", word(lit("...")))))))), nil, "$1: cannot assign ", false}, 166 // string length 167 {word(paramExp(lit("V"), "#", nil)), []string{strconv.Itoa(len(V))}, "", false}, 168 {word(paramExp(lit("E"), "#", nil)), []string{"0"}, "", false}, 169 {word(paramExp(lit("_"), "#", nil)), nil, "$_: parameter is unset", false}, 170 // remove suffix pattern 171 {word(paramExp(lit("P"), "%", word(lit("/*")))), []string{"foo/bar"}, "", false}, 172 {word(paramExp(lit("P"), "%%", word(lit("/*")))), []string{"foo"}, "", false}, 173 {word(paramExp(lit("P"), "%", word(quote(`'`, word(lit("/*")))))), []string{"foo/bar/baz"}, "", false}, 174 {word(paramExp(lit("P"), "%%", word(quote(`'`, word(lit("/*")))))), []string{"foo/bar/baz"}, "", false}, 175 {word(paramExp(lit("P"), "%", word())), []string{"foo/bar/baz"}, "", false}, 176 {word(paramExp(lit("P"), "%%", word())), []string{"foo/bar/baz"}, "", false}, 177 {word(paramExp(lit("_"), "%", word())), nil, "$_: parameter is unset", false}, 178 {word(paramExp(lit("_"), "%%", word())), nil, "$_: parameter is unset", false}, 179 180 {word(paramExp(lit("V"), "%", word(quote(`"`, word(paramExp(lit("1"), "=", word(lit("...")))))))), nil, "$1: cannot assign ", false}, 181 {word(paramExp(lit("V"), "%%", word(quote(`"`, word(paramExp(lit("1"), ":=", word(lit("...")))))))), nil, "$1: cannot assign ", false}, 182 {word(paramExp(lit("V"), "%", word(lit("\xff")))), nil, "regexp: invalid UTF-8", false}, 183 {word(paramExp(lit("V"), "%%", word(lit("\xff")))), nil, "regexp: invalid UTF-8", false}, 184 // remove prefix pattern 185 {word(paramExp(lit("P"), "#", word(lit("*/")))), []string{"bar/baz"}, "", false}, 186 {word(paramExp(lit("P"), "##", word(lit("*/")))), []string{"baz"}, "", false}, 187 {word(paramExp(lit("P"), "#", word(quote(`'`, word(lit("*/")))))), []string{"foo/bar/baz"}, "", false}, 188 {word(paramExp(lit("P"), "##", word(quote(`'`, word(lit("*/")))))), []string{"foo/bar/baz"}, "", false}, 189 {word(paramExp(lit("P"), "#", word())), []string{"foo/bar/baz"}, "", false}, 190 {word(paramExp(lit("P"), "##", word())), []string{"foo/bar/baz"}, "", false}, 191 {word(paramExp(lit("_"), "#", word())), nil, "$_: parameter is unset", false}, 192 {word(paramExp(lit("_"), "##", word())), nil, "$_: parameter is unset", false}, 193 194 {word(paramExp(lit("V"), "#", word(quote(`"`, word(paramExp(lit("1"), "=", word(lit("...")))))))), nil, "$1: cannot assign ", false}, 195 {word(paramExp(lit("V"), "##", word(quote(`"`, word(paramExp(lit("1"), ":=", word(lit("...")))))))), nil, "$1: cannot assign ", false}, 196 {word(paramExp(lit("V"), "#", word(lit("\xff")))), nil, "regexp: invalid UTF-8", false}, 197 {word(paramExp(lit("V"), "##", word(lit("\xff")))), nil, "regexp: invalid UTF-8", false}, 198 } 199 200 var spParamTests = []struct { 201 word ast.Word 202 mode interp.ExpMode 203 args []string 204 ifs interface{} 205 fields []string 206 err string 207 assign string 208 }{ 209 // simplest form 210 {word(paramExp(lit("@"), "", nil)), 0, nil, nil, nil, "", ""}, 211 {word(paramExp(lit("@"), "", nil)), 0, []string{""}, nil, nil, "", ""}, 212 {word(quote(`"`, word(paramExp(lit("@"), "", nil)))), 0, []string{""}, nil, []string{""}, "", ""}, 213 {word(paramExp(lit("@"), "", nil)), 0, []string{"1"}, nil, []string{"1"}, "", ""}, 214 {word(paramExp(lit("@"), "", nil)), 0, []string{"1", "2"}, nil, []string{"1", "2"}, "", ""}, 215 {word(paramExp(lit("@"), "", nil)), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""}, 216 {word(quote(`"`, word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""}, 217 218 {word(paramExp(lit("*"), "", nil)), 0, nil, nil, nil, "", ""}, 219 {word(paramExp(lit("*"), "", nil)), 0, []string{""}, nil, nil, "", ""}, 220 {word(quote(`"`, word(paramExp(lit("*"), "", nil)))), 0, []string{""}, nil, []string{""}, "", ""}, 221 {word(paramExp(lit("*"), "", nil)), 0, []string{"1"}, nil, []string{"1"}, "", ""}, 222 {word(paramExp(lit("*"), "", nil)), 0, []string{"1", "2"}, nil, []string{"1", "2"}, "", ""}, 223 {word(paramExp(lit("*"), "", nil)), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""}, 224 {word(quote(`"`, word(paramExp(lit("*"), "", nil)))), 0, []string{"", "2", ""}, nil, []string{" 2 "}, "", ""}, 225 {word(quote(`"`, word(paramExp(lit("*"), "", nil)))), 0, []string{"", "2", ""}, ", \t\n", []string{",2,"}, "", ""}, 226 {word(quote(`"`, word(paramExp(lit("*"), "", nil)))), 0, []string{"", "2", ""}, "", []string{"2"}, "", ""}, 227 228 {word(paramExp(lit("@"), "", nil)), interp.Literal, []string{"*", "?"}, nil, []string{"* ?"}, "", ""}, 229 {word(paramExp(lit("@"), "", nil)), interp.Literal, []string{"*", "?"}, ", \t\n", []string{"*,?"}, "", ""}, 230 {word(paramExp(lit("@"), "", nil)), interp.Literal, []string{"*", "?"}, "", []string{"*?"}, "", ""}, 231 {word(quote(`"`, word(paramExp(lit("@"), "", nil)))), interp.Literal, []string{"*", "?"}, nil, []string{"* ?"}, "", ""}, 232 {word(quote(`"`, word(paramExp(lit("@"), "", nil)))), interp.Literal, []string{"*", "?"}, ", \t\n", []string{"*,?"}, "", ""}, 233 {word(quote(`"`, word(paramExp(lit("@"), "", nil)))), interp.Literal, []string{"*", "?"}, "", []string{"*?"}, "", ""}, 234 235 {word(paramExp(lit("@"), "", nil)), interp.Pattern, []string{"*", "?"}, nil, []string{"* ?"}, "", ""}, 236 {word(paramExp(lit("@"), "", nil)), interp.Pattern, []string{"*", "?"}, ", \t\n", []string{"*,?"}, "", ""}, 237 {word(paramExp(lit("@"), "", nil)), interp.Pattern, []string{"*", "?"}, "", []string{"*?"}, "", ""}, 238 {word(quote(`"`, word(paramExp(lit("@"), "", nil)))), interp.Pattern, []string{"*", "?"}, nil, []string{`\* \?`}, "", ""}, 239 {word(quote(`"`, word(paramExp(lit("@"), "", nil)))), interp.Pattern, []string{"*", "?"}, ", \t\n", []string{`\*,\?`}, "", ""}, 240 {word(quote(`"`, word(paramExp(lit("@"), "", nil)))), interp.Pattern, []string{"*", "?"}, "", []string{`\*\?`}, "", ""}, 241 // use default values 242 {word(paramExp(lit("@"), ":-", word(lit("...")))), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""}, 243 {word(paramExp(lit("_"), ":-", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""}, 244 {word(paramExp(lit("_"), ":-", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""}, 245 {word(quote(`"`, word(paramExp(lit("_"), ":-", word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""}, 246 {word(quote(`"`, word(paramExp(lit("_"), ":-", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""}, 247 // assign default values 248 {word(paramExp(lit("@"), ":=", word(lit("...")))), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""}, 249 {word(paramExp(lit("_"), ":=", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, nil, []string{"2"}, "", " 2 "}, 250 {word(paramExp(lit("_"), ":=", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, ", \n\t", []string{"2"}, "", ",2,"}, 251 {word(paramExp(lit("_"), ":=", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, "", []string{"2"}, "", "2"}, 252 {word(paramExp(lit("_"), ":=", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", " 2 "}, 253 {word(quote(`"`, word(paramExp(lit("_"), ":=", word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", " 2 "}, 254 {word(quote(`"`, word(paramExp(lit("_"), ":=", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", " 2 "}, 255 // indicate error if unset or null 256 {word(paramExp(lit("@"), ":?", word(lit("...")))), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""}, 257 {word(paramExp(lit("_"), ":?", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, nil, nil, "$_: 2 ", ""}, 258 {word(paramExp(lit("_"), ":?", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, ", \n\t", nil, "$_: ,2,", ""}, 259 {word(paramExp(lit("_"), ":?", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, "", nil, "$_: 2", ""}, 260 {word(paramExp(lit("_"), ":?", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, nil, "$_: 2 ", ""}, 261 {word(quote(`"`, word(paramExp(lit("_"), ":?", word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, nil, "$_: 2 ", ""}, 262 {word(quote(`"`, word(paramExp(lit("_"), ":?", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))))), 0, []string{"", "2", ""}, nil, nil, "$_: 2 ", ""}, 263 // use alternative values 264 {word(paramExp(lit("@"), ":+", word(lit("...")))), 0, []string{"", "2", ""}, nil, []string{"..."}, "", ""}, 265 {word(paramExp(lit("V"), ":+", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""}, 266 {word(paramExp(lit("V"), ":+", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""}, 267 {word(quote(`"`, word(paramExp(lit("V"), ":+", word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""}, 268 {word(quote(`"`, word(paramExp(lit("V"), ":+", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""}, 269 // string length 270 {word(paramExp(lit("@"), "#", nil)), 0, nil, nil, []string{"0"}, "", ""}, 271 {word(paramExp(lit("@"), "#", nil)), 0, []string{""}, nil, []string{"1"}, "", ""}, 272 {word(paramExp(lit("@"), "#", nil)), 0, []string{"1"}, nil, []string{"1"}, "", ""}, 273 {word(paramExp(lit("@"), "#", nil)), 0, []string{"1", "2"}, nil, []string{"2"}, "", ""}, 274 // remove suffix pattern 275 {word(paramExp(lit("@"), "%", word(lit("/*")))), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"foo/bar", "qux"}, "", ""}, 276 {word(paramExp(lit("@"), "%%", word(lit("/*")))), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"foo", "qux"}, "", ""}, 277 {word(paramExp(lit("@"), "%", word())), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"foo/bar/baz", "qux/quux"}, "", ""}, 278 {word(paramExp(lit("@"), "%%", word())), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"foo/bar/baz", "qux/quux"}, "", ""}, 279 280 {word(paramExp(lit("P"), "%", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "*"}, "/", []string{"foo", "bar"}, "", ""}, 281 {word(paramExp(lit("P"), "%%", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "*"}, "/", []string{"foo"}, "", ""}, 282 {word(quote(`"`, word(paramExp(lit("P"), "%", word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "*"}, "/", []string{"foo/bar"}, "", ""}, 283 {word(quote(`"`, word(paramExp(lit("P"), "%%", word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "*"}, "/", []string{"foo"}, "", ""}, 284 // remove prefix pattern 285 {word(paramExp(lit("@"), "#", word(lit("*/")))), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"bar/baz", "quux"}, "", ""}, 286 {word(paramExp(lit("@"), "##", word(lit("*/")))), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"baz", "quux"}, "", ""}, 287 {word(paramExp(lit("@"), "#", word())), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"foo/bar/baz", "qux/quux"}, "", ""}, 288 {word(paramExp(lit("@"), "##", word())), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"foo/bar/baz", "qux/quux"}, "", ""}, 289 290 {word(paramExp(lit("P"), "#", word(paramExp(lit("@"), "", nil)))), 0, []string{"*", ""}, "/", []string{"bar", "baz"}, "", ""}, 291 {word(paramExp(lit("P"), "##", word(paramExp(lit("@"), "", nil)))), 0, []string{"*", ""}, "/", []string{"baz"}, "", ""}, 292 {word(quote(`"`, word(paramExp(lit("P"), "#", word(paramExp(lit("@"), "", nil)))))), 0, []string{"*", ""}, "/", []string{"bar/baz"}, "", ""}, 293 {word(quote(`"`, word(paramExp(lit("P"), "##", word(paramExp(lit("@"), "", nil)))))), 0, []string{"*", ""}, "/", []string{"baz"}, "", ""}, 294 } 295 296 var posParamTests = []struct { 297 word ast.Word 298 args []string 299 fields []string 300 }{ 301 {word(paramExp(lit("1"), "", nil)), []string{}, nil}, 302 {word(paramExp(lit("1"), "", nil)), []string{""}, nil}, 303 {word(quote(`"`, word(paramExp(lit("1"), "", nil)))), []string{""}, []string{""}}, 304 {word(paramExp(lit("1"), "", nil)), []string{"1"}, []string{"1"}}, 305 {word(paramExp(lit("1"), "", nil)), []string{"1", "2"}, []string{"1"}}, 306 307 {word(paramExp(lit("01"), "", nil)), []string{}, nil}, 308 {word(paramExp(lit("01"), "", nil)), []string{""}, nil}, 309 {word(quote(`"`, word(paramExp(lit("01"), "", nil)))), []string{""}, []string{""}}, 310 {word(paramExp(lit("01"), "", nil)), []string{"1"}, []string{"1"}}, 311 {word(paramExp(lit("01"), "", nil)), []string{"1", "2"}, []string{"1"}}, 312 313 {word(paramExp(lit("2"), "", nil)), []string{}, nil}, 314 {word(paramExp(lit("2"), "", nil)), []string{"1"}, nil}, 315 {word(paramExp(lit("2"), "", nil)), []string{"1", "2"}, []string{"2"}}, 316 {word(paramExp(lit("2"), "", nil)), []string{"1", "2", "3"}, []string{"2"}}, 317 318 {word(paramExp(lit("02"), "", nil)), []string{}, nil}, 319 {word(paramExp(lit("02"), "", nil)), []string{"1"}, nil}, 320 {word(paramExp(lit("02"), "", nil)), []string{"1", "2"}, []string{"2"}}, 321 {word(paramExp(lit("02"), "", nil)), []string{"1", "2", "3"}, []string{"2"}}, 322 } 323 324 var arithExpTests = []struct { 325 word ast.Word 326 fields []string 327 err string 328 }{ 329 {word(arithExp(word(lit("2 - 1")))), []string{"1"}, ""}, 330 {word(quote(`"`, word(arithExp(word(lit("1 + 1")))))), []string{"2"}, ""}, 331 {word(arithExp(word(quote(`"`, word(lit("6 / 2")))))), []string{"3"}, ""}, 332 {word(arithExp(word(lit("1 << "), quote(`'`, word(lit("2")))))), []string{"4"}, ""}, 333 334 {word(arithExp(word(lit("E")))), []string{"0"}, ""}, 335 {word(arithExp(word(quote(`'`, word(lit("E")))))), []string{"0"}, ""}, 336 {word(arithExp(word(lit("1 + E")))), []string{"1"}, ""}, 337 {word(arithExp(word(lit("1 + "), quote(`'`, word(lit("E")))))), []string{"1"}, ""}, 338 {word(arithExp(word(lit("++E + 1")))), []string{"2"}, ""}, 339 {word(arithExp(word(lit("++"), quote(`'`, word(lit("E"))), lit(" + 1")))), []string{"2"}, ""}, 340 341 {word(arithExp(word(paramExp(lit("E"), "", nil)))), []string{"0"}, ""}, 342 {word(arithExp(word(quote(`"`, word(paramExp(lit("E"), "", nil)))))), []string{"0"}, ""}, 343 {word(arithExp(word(lit("1 + "), paramExp(lit("E"), "", nil)))), []string{"1"}, ""}, 344 {word(arithExp(word(lit("1 + "), quote(`"`, word(paramExp(lit("E"), "", nil)))))), []string{"1"}, ""}, 345 {word(arithExp(word(lit("++"), paramExp(lit("E"), "", nil), lit(" + 1")))), []string{"2"}, ""}, 346 {word(arithExp(word(lit("++"), quote(`"`, word(paramExp(lit("E"), "", nil))), lit(" + 1")))), []string{"2"}, ""}, 347 {word(arithExp(word(paramExp(lit("E"), ":-", word(arithExp(word(lit("7 % 4")))))))), []string{"3"}, ""}, 348 349 {word(arithExp(word(lit("1 * "), arithExp(word(lit("2 + 3")))))), []string{"5"}, ""}, 350 {word(arithExp(word(lit("1 * "), quote(`"`, word(arithExp(word(lit("2 + 3")))))))), []string{"5"}, ""}, 351 352 {word(arithExp(word())), nil, "arithmetic expression is missing"}, 353 {word(arithExp(word(paramExp(lit("9"), ":=", word(lit("...")))))), nil, "$9: cannot assign "}, 354 {word(arithExp(word(paramExp(lit("@"), "", nil)))), nil, "1 2 3: unexpected NUMBER"}, 355 {word(arithExp(word(paramExp(lit("#"), "", nil), lit("++")))), nil, "3++: '++' requires lvalue"}, 356 {word(arithExp(word(paramExp(lit("1"), "", nil), lit("--")))), nil, "1--: '--' requires lvalue"}, 357 } 358 359 var fieldSplitTests = []struct { 360 word ast.Word 361 ifs interface{} 362 fields []string 363 }{ 364 {word(lit(" \t abc \t xyz \t ")), nil, []string{"abc", "xyz"}}, 365 {word(lit(" \t abc \t, \t ,\t xyz \t ")), " \t\n,", []string{"abc", "xyz"}}, 366 {word(lit(" \t abc \t, "), quote(`"`, word()), lit(" ,\t xyz \t ")), " \t\n,", []string{"abc", "", "xyz"}}, 367 {word(lit(" \t,abc \t xyz \t,")), " \t\n,", []string{"abc", "xyz"}}, 368 {word(quote(`"`, word()), lit("\t,abc \t xyz \t,")), " \t\n,", []string{"", "abc", "xyz"}}, 369 {word(lit(" \t,abc \t xyz \t,"), quote(`"`, word())), " \t\n,", []string{"abc", "xyz", ""}}, 370 {word(lit("abc \xff xyz")), nil, []string{"abc", "\xff", "xyz"}}, 371 {word(lit("abc \t xyz")), "", []string{"abc \t xyz"}}, 372 {word(quote(`'`, word(lit("abc \t xyz")))), "", []string{"abc \t xyz"}}, 373 {word(quote(`"`, word(lit("abc \t xyz")))), "", []string{"abc \t xyz"}}, 374 } 375 376 var pathExpTests = []struct { 377 word ast.Word 378 opts interp.Option 379 fields []string 380 }{ 381 {word(), 0, nil}, 382 {word(lit("foo")), 0, []string{"foo"}}, 383 {word(lit("qux")), 0, []string{"qux"}}, 384 385 {word(lit("b*")), 0, []string{"bar", "baz"}}, 386 {word(lit("b*")), interp.NoGlob, []string{"b*"}}, 387 {word(lit("b"), quote(`\`, word(lit("*")))), 0, []string{"b*"}}, 388 {word(quote(`'`, word(lit("b*")))), 0, []string{"b*"}}, 389 {word(quote(`"`, word(lit("b*")))), 0, []string{"b*"}}, 390 391 {word(lit("q*")), 0, []string{"q*"}}, 392 {word(lit("q*")), interp.NoGlob, []string{"q*"}}, 393 {word(lit("q"), quote(`\`, word(lit("*")))), 0, []string{"q*"}}, 394 {word(quote(`'`, word(lit("q*")))), 0, []string{"q*"}}, 395 {word(quote(`"`, word(lit("q*")))), 0, []string{"q*"}}, 396 397 {word(lit("\xff*")), 0, []string{"\xff*"}}, 398 {word(lit("\xff*")), interp.NoGlob, []string{"\xff*"}}, 399 {word(lit("\xff"), quote(`\`, word(lit("*")))), 0, []string{"\xff*"}}, 400 {word(quote(`'`, word(lit("\xff*")))), 0, []string{"\xff*"}}, 401 {word(quote(`"`, word(lit("\xff*")))), 0, []string{"\xff*"}}, 402 } 403 404 func TestExpand(t *testing.T) { 405 env := interp.NewExecEnv(name) 406 env.Set("E", E) 407 for _, tt := range expandTests { 408 g, _ := env.Expand(tt.word, tt.mode) 409 if e := tt.fields; !reflect.DeepEqual(g, e) { 410 t.Errorf("expected %#v, got %#v", e, g) 411 } 412 } 413 t.Run("TildeExp", func(t *testing.T) { 414 env := interp.NewExecEnv(name) 415 env.Set("E", E) 416 env.Unset("_") 417 for _, tt := range tildeExpTests { 418 if runtime.GOOS != "windows" && strings.ContainsRune(tt.fields[0], '\\') { 419 continue 420 } 421 g, _ := env.Expand(tt.word, tt.mode) 422 if e := tt.fields; !reflect.DeepEqual(g, e) { 423 t.Errorf("expected %#v, got %#v", e, g) 424 } 425 } 426 }) 427 t.Run("ParamExp", func(t *testing.T) { 428 for _, tt := range paramExpTests { 429 env := interp.NewExecEnv(name) 430 env.Opts |= interp.NoUnset 431 env.Set("V", V) 432 env.Set("E", E) 433 env.Set("P", P) 434 env.Unset("_") 435 g, err := env.Expand(tt.word, interp.Quote) 436 switch { 437 case err == nil && tt.err != "": 438 t.Error("expected error") 439 case err != nil && (tt.err == "" || !strings.Contains(err.Error(), tt.err)): 440 t.Error("unexpected error:", err) 441 default: 442 if e := tt.fields; !reflect.DeepEqual(g, e) { 443 t.Errorf("expected %#v, got %#v", e, g) 444 } 445 if tt.assign { 446 pe := tt.word[0].(*ast.ParamExp) 447 if v, set := env.Get(pe.Name.Value); !set { 448 t.Errorf("%v is unset", pe.Name.Value) 449 } else { 450 var b strings.Builder 451 printer.Fprint(&b, pe.Word) 452 if g, e := v.Value, b.String(); g != e { 453 t.Errorf("expected %q, got %q", e, g) 454 } 455 } 456 } 457 } 458 } 459 }) 460 t.Run("SpParam", func(t *testing.T) { 461 for _, tt := range spParamTests { 462 env := interp.NewExecEnv(name, tt.args...) 463 env.Set("V", V) 464 env.Set("P", P) 465 env.Unset("_") 466 if tt.ifs != nil { 467 env.Set("IFS", tt.ifs.(string)) 468 } else { 469 env.Unset("IFS") 470 } 471 g, err := env.Expand(tt.word, tt.mode) 472 switch { 473 case err == nil && tt.err != "": 474 t.Error("expected error") 475 case err != nil && (tt.err == "" || !strings.Contains(err.Error(), tt.err)): 476 t.Error("unexpected error:", err) 477 default: 478 if e := tt.fields; !reflect.DeepEqual(g, e) { 479 t.Errorf("expected %#v, got %#v", e, g) 480 } 481 if tt.assign != "" { 482 pe, ok := tt.word[0].(*ast.ParamExp) 483 if !ok { 484 pe = tt.word[0].(*ast.Quote).Value[0].(*ast.ParamExp) 485 } 486 if v, set := env.Get(pe.Name.Value); !set { 487 t.Errorf("%v is unset", pe.Name.Value) 488 } else { 489 if g, e := v.Value, tt.assign; g != e { 490 t.Errorf("expected %q, got %q", e, g) 491 } 492 } 493 } 494 } 495 } 496 }) 497 t.Run("PosParam", func(t *testing.T) { 498 for _, tt := range posParamTests { 499 env := interp.NewExecEnv(name, tt.args...) 500 g, _ := env.Expand(tt.word, 0) 501 if e := tt.fields; !reflect.DeepEqual(g, e) { 502 t.Errorf("expected %#v, got %#v", e, g) 503 } 504 } 505 }) 506 t.Run("ArithExp", func(t *testing.T) { 507 for _, tt := range arithExpTests { 508 env := interp.NewExecEnv(name, "1", "2", "3") 509 env.Set("E", E) 510 g, err := env.Expand(tt.word, 0) 511 switch { 512 case err == nil && tt.err != "": 513 t.Error("expected error") 514 case err != nil && (tt.err == "" || !strings.Contains(err.Error(), tt.err)): 515 t.Error("unexpected error:", err) 516 default: 517 if e := tt.fields; !reflect.DeepEqual(g, e) { 518 t.Errorf("expected %#v, got %#v", e, g) 519 } 520 } 521 } 522 }) 523 t.Run("FieldSplit", func(t *testing.T) { 524 for _, tt := range fieldSplitTests { 525 env := interp.NewExecEnv(name) 526 if tt.ifs != nil { 527 env.Set("IFS", tt.ifs.(string)) 528 } else { 529 env.Unset("IFS") 530 } 531 g, _ := env.Expand(tt.word, 0) 532 if e := tt.fields; !reflect.DeepEqual(g, e) { 533 t.Errorf("expected %#v, got %#v", e, g) 534 } 535 } 536 }) 537 t.Run("PathExp", func(t *testing.T) { 538 popd, err := pushd(t.TempDir()) 539 if err != nil { 540 t.Fatal(err) 541 } 542 defer popd() 543 544 for _, name := range []string{"foo", "bar", "baz"} { 545 if err := touch(name); err != nil { 546 t.Fatal(err) 547 } 548 } 549 550 for _, tt := range pathExpTests { 551 env := interp.NewExecEnv(name) 552 env.Opts = tt.opts 553 g, _ := env.Expand(tt.word, 0) 554 if e := tt.fields; !reflect.DeepEqual(g, e) { 555 t.Errorf("expected %#v, got %#v", e, g) 556 } 557 } 558 }) 559 } 560 561 func word(w ...ast.WordPart) ast.Word { 562 if len(w) == 0 { 563 return ast.Word{} 564 } 565 return ast.Word(w) 566 } 567 568 func lit(s string) *ast.Lit { 569 return &ast.Lit{Value: s} 570 } 571 572 func litf(format string, a ...interface{}) *ast.Lit { 573 return &ast.Lit{Value: fmt.Sprintf(format, a...)} 574 } 575 576 func quote(tok string, word ast.Word) *ast.Quote { 577 return &ast.Quote{ 578 Tok: tok, 579 Value: word, 580 } 581 } 582 583 func paramExp(name *ast.Lit, op string, word ast.Word) *ast.ParamExp { 584 return &ast.ParamExp{ 585 Name: name, 586 Op: op, 587 Word: word, 588 } 589 } 590 591 func arithExp(expr ast.Word) *ast.ArithExp { 592 return &ast.ArithExp{Expr: expr} 593 } 594 595 func username() string { 596 u, err := user.Current() 597 if err != nil { 598 panic(err) 599 } 600 return u.Username 601 } 602 603 func homeDir() string { 604 u, err := user.Current() 605 if err != nil { 606 panic(err) 607 } 608 return filepath.ToSlash(u.HomeDir) 609 }