xref: /aosp_15_r20/build/blueprint/proptools/escape_test.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
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