1// Copyright 2015 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package proptools 16 17import ( 18 "bytes" 19 "os/exec" 20 "reflect" 21 "slices" 22 "testing" 23 "unsafe" 24) 25 26type escapeTestCase struct { 27 name string 28 in string 29 out string 30} 31 32var ninjaEscapeTestCase = []escapeTestCase{ 33 { 34 name: "no escaping", 35 in: `test`, 36 out: `test`, 37 }, 38 { 39 name: "leading $", 40 in: `$test`, 41 out: `$$test`, 42 }, 43 { 44 name: "trailing $", 45 in: `test$`, 46 out: `test$$`, 47 }, 48 { 49 name: "leading and trailing $", 50 in: `$test$`, 51 out: `$$test$$`, 52 }, 53} 54 55var shellEscapeTestCase = []escapeTestCase{ 56 { 57 name: "no escaping", 58 in: `test`, 59 out: `test`, 60 }, 61 { 62 name: "leading $", 63 in: `$test`, 64 out: `'$test'`, 65 }, 66 { 67 name: "trailing $", 68 in: `test$`, 69 out: `'test$'`, 70 }, 71 { 72 name: "leading and trailing $", 73 in: `$test$`, 74 out: `'$test$'`, 75 }, 76 { 77 name: "single quote", 78 in: `'`, 79 out: `''\'''`, 80 }, 81 { 82 name: "multiple single quote", 83 in: `''`, 84 out: `''\'''\'''`, 85 }, 86 { 87 name: "double quote", 88 in: `""`, 89 out: `'""'`, 90 }, 91 { 92 name: "ORIGIN", 93 in: `-Wl,--rpath,${ORIGIN}/../bionic-loader-test-libs`, 94 out: `'-Wl,--rpath,${ORIGIN}/../bionic-loader-test-libs'`, 95 }, 96} 97 98var shellEscapeIncludingSpacesTestCase = []escapeTestCase{ 99 { 100 name: "no escaping", 101 in: `test`, 102 out: `test`, 103 }, 104 { 105 name: "spacing", 106 in: `arg1 arg2`, 107 out: `'arg1 arg2'`, 108 }, 109 { 110 name: "single quote", 111 in: `'arg'`, 112 out: `''\''arg'\'''`, 113 }, 114} 115 116func TestNinjaEscaping(t *testing.T) { 117 for _, testCase := range ninjaEscapeTestCase { 118 got := NinjaEscape(testCase.in) 119 if got != testCase.out { 120 t.Errorf("%s: expected `%s` got `%s`", testCase.name, testCase.out, got) 121 } 122 } 123} 124 125func TestShellEscaping(t *testing.T) { 126 for _, testCase := range shellEscapeTestCase { 127 got := ShellEscape(testCase.in) 128 if got != testCase.out { 129 t.Errorf("%s: expected `%s` got `%s`", testCase.name, testCase.out, got) 130 } 131 } 132} 133 134func TestShellEscapeIncludingSpaces(t *testing.T) { 135 for _, testCase := range shellEscapeIncludingSpacesTestCase { 136 got := ShellEscapeIncludingSpaces(testCase.in) 137 if got != testCase.out { 138 t.Errorf("%s: expected `%s` got `%s`", testCase.name, testCase.out, got) 139 } 140 } 141} 142 143func TestExternalShellEscaping(t *testing.T) { 144 if testing.Short() { 145 return 146 } 147 for _, testCase := range shellEscapeTestCase { 148 cmd := "echo " + ShellEscape(testCase.in) 149 got, err := exec.Command("/bin/sh", "-c", cmd).Output() 150 got = bytes.TrimSuffix(got, []byte("\n")) 151 if err != nil { 152 t.Error(err) 153 } 154 if string(got) != testCase.in { 155 t.Errorf("%s: expected `%s` got `%s`", testCase.name, testCase.in, got) 156 } 157 } 158} 159 160func TestExternalShellEscapeIncludingSpaces(t *testing.T) { 161 if testing.Short() { 162 return 163 } 164 for _, testCase := range shellEscapeIncludingSpacesTestCase { 165 cmd := "echo " + ShellEscapeIncludingSpaces(testCase.in) 166 got, err := exec.Command("/bin/sh", "-c", cmd).Output() 167 got = bytes.TrimSuffix(got, []byte("\n")) 168 if err != nil { 169 t.Error(err) 170 } 171 if string(got) != testCase.in { 172 t.Errorf("%s: expected `%s` got `%s`", testCase.name, testCase.in, got) 173 } 174 } 175} 176 177func TestNinjaEscapeList(t *testing.T) { 178 type testCase struct { 179 name string 180 in []string 181 ninjaEscaped []string 182 shellEscaped []string 183 ninjaAndShellEscaped []string 184 sameSlice bool 185 } 186 testCases := []testCase{ 187 { 188 name: "empty", 189 in: []string{}, 190 sameSlice: true, 191 }, 192 { 193 name: "nil", 194 in: nil, 195 sameSlice: true, 196 }, 197 { 198 name: "no escaping", 199 in: []string{"abc", "def", "ghi"}, 200 sameSlice: true, 201 }, 202 { 203 name: "escape first", 204 in: []string{`$\abc`, "def", "ghi"}, 205 ninjaEscaped: []string{`$$\abc`, "def", "ghi"}, 206 shellEscaped: []string{`'$\abc'`, "def", "ghi"}, 207 ninjaAndShellEscaped: []string{`'$$\abc'`, "def", "ghi"}, 208 }, 209 { 210 name: "escape middle", 211 in: []string{"abc", `$\def`, "ghi"}, 212 ninjaEscaped: []string{"abc", `$$\def`, "ghi"}, 213 shellEscaped: []string{"abc", `'$\def'`, "ghi"}, 214 ninjaAndShellEscaped: []string{"abc", `'$$\def'`, "ghi"}, 215 }, 216 { 217 name: "escape last", 218 in: []string{"abc", "def", `$\ghi`}, 219 ninjaEscaped: []string{"abc", "def", `$$\ghi`}, 220 shellEscaped: []string{"abc", "def", `'$\ghi'`}, 221 ninjaAndShellEscaped: []string{"abc", "def", `'$$\ghi'`}, 222 }, 223 } 224 225 testFuncs := []struct { 226 name string 227 f func([]string) []string 228 expected func(tt testCase) []string 229 }{ 230 {name: "NinjaEscapeList", f: NinjaEscapeList, expected: func(tt testCase) []string { return tt.ninjaEscaped }}, 231 {name: "ShellEscapeList", f: ShellEscapeList, expected: func(tt testCase) []string { return tt.shellEscaped }}, 232 {name: "NinjaAndShellEscapeList", f: NinjaAndShellEscapeList, expected: func(tt testCase) []string { return tt.ninjaAndShellEscaped }}, 233 } 234 235 for _, tf := range testFuncs { 236 t.Run(tf.name, func(t *testing.T) { 237 for _, tt := range testCases { 238 t.Run(tt.name, func(t *testing.T) { 239 inCopy := slices.Clone(tt.in) 240 241 got := tf.f(tt.in) 242 243 want := tf.expected(tt) 244 if tt.sameSlice { 245 want = tt.in 246 } 247 248 if !reflect.DeepEqual(got, want) { 249 t.Errorf("incorrect output, want %q got %q", want, got) 250 } 251 if len(inCopy) != len(tt.in) && (len(tt.in) == 0 || !reflect.DeepEqual(inCopy, tt.in)) { 252 t.Errorf("input modified, want %#v, got %#v", inCopy, tt.in) 253 } 254 255 if (unsafe.SliceData(tt.in) == unsafe.SliceData(got)) != tt.sameSlice { 256 if tt.sameSlice { 257 t.Errorf("expected input and output slices to have the same backing arrays") 258 } else { 259 t.Errorf("expected input and output slices to have different backing arrays") 260 } 261 } 262 }) 263 } 264 }) 265 } 266} 267