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