OILS / stdlib / ysh / quote-test.ysh View on Github | oils.pub

280 lines, 134 significant
1#!bin/ysh
2
3use $LIB_YSH/quote.ysh
4use $LIB_YSH/yblocks.ysh --pick yb-capture
5
6# Change to 'use'?
7source $LIB_OSH/byo-server.sh
8
9proc test-sh {
10 assert ["'x'" === quote.sh('x')]
11 assert [sq_expected === quote.sh("'")]
12}
13
14proc test-shell {
15 echo TODO
16
17 #assert ["'x'" === quote.shell('x')]
18 # Note: the \\ is hard to read - '' doesn't help
19 #assert [sq_expected === quote.shell("'")]
20 #assert ["" === quote.shell(b'\n')]
21}
22
23proc test-ninja {
24 assert ['dir/my-file.txt' === quote.ninja('dir/my-file.txt')]
25
26 assert ['$$' === quote.ninja('$')]
27 assert ['$ ' === quote.ninja(' ')]
28 assert ['$:' === quote.ninja(':')]
29 try {
30 call quote.ninja(u'\n')
31 }
32 assert [10 === _error.code]
33}
34
35proc test-make {
36 assert ['dir/my-file.txt' === quote.make('dir/my-file.txt')]
37
38 # https://www.cmcrossroads.com/article/gnu-make-escaping-walk-wild-side
39
40 # weird special case
41 assert ['$$' === quote.make('$')]
42
43 # % appears in 3 places, esacped by \
44 assert [r'\%' === quote.make('%')]
45 assert [r'\\' === quote.make(r'\')]
46
47 assert [r'\[' === quote.make('[')]
48 assert [r'\]' === quote.make(']')]
49 assert [r'\*' === quote.make('*')]
50 assert [r'\?' === quote.make('?')]
51
52 try {
53 call quote.make(u'\n')
54 }
55 assert [10 === _error.code]
56}
57
58var REAL_CASES = [
59 'Z', # ASCII
60 '', # empty string
61 # TODO: This should be explicitly disallowed
62 #b'\y00', # NUL byte
63 b'\y07', # low unprintable byte
64 b'\yff', # high unprintable byte
65
66 # whitespace
67 ' ',
68 u'\t',
69 # Ninja doesn't allow newlines in filenames
70 #u'\r',
71 #u'\n',
72
73 # JSON stuff
74 u'\b',
75 u'\f',
76 # Note: JSON has no \v
77
78 # Common metacharacters
79 '"',
80 "'",
81 r'\', # C-style, JSON
82 '$', # make/ninja
83 '&', # HTML
84 '%', # URL
85 '^', # line continuation for Windows batch
86 ':', # ninja character
87 #'#', # comment character - Ninja has no way to escape!
88
89 u'\u{3bc}', # UTF-8 2 bytes - mu
90 u'\u{4e09}', # UTF-8 3 bytes - Chinese 3
91 u'\u{1f618}', # UTF-8 4 bytes - Emoji
92
93 # surrogate range? May be round tripped as byte strings
94]
95
96#var REAL_CASES = [ 'a', u'\n', 'b' ]
97
98
99const REAL_JOINED = join(REAL_CASES, '') ++ u'\n'
100const REAL_JOINED_SPACE = join(REAL_CASES, ' ') ++ u'\n' # for shell
101
102proc test-round-trip-sh {
103 # TODO: our test framework needs a temp dir
104 var dir = '_tmp/quote-test'
105 mkdir -p $dir
106 cd $dir
107
108 var parts = ['echo']
109 for s in (REAL_CASES) {
110 call parts->append(quote.sh(s))
111 }
112
113 echo $[join(parts, ' ')] > round-trip.sh
114
115 yb-capture (&r) {
116 # Hm bash says: cannot execute binary file!
117
118 # The \0 byte is not round trippable in /bin/sh
119 # So we should not allow it
120 # We should exclude it
121
122 /bin/sh round-trip.sh
123 }
124 = r.stdout
125 assert [REAL_JOINED_SPACE === r.stdout]
126}
127
128proc test-round-trip-js {
129 # TODO: we need node.js in the CI to test this, or some other JS interpreter
130 # like duktape
131 return
132
133 # 0xff is not representable, as expected
134
135 # TODO: our test framework needs a temp dir
136 var dir = '_tmp/quote-test'
137 mkdir -p $dir
138 cd $dir
139
140 var parts = []
141 for s in (REAL_CASES) {
142 call parts->append(toJson(s))
143 }
144
145 echo "console.log($[join(parts, ' +" "+ ')])" > round-trip.js
146
147 yb-capture (&r) {
148 # Hm bash says: cannot execute binary file!
149
150 # The \0 byte is not round trippable in /bin/sh
151 # So we should not allow it
152 # We should exclude it
153
154 nodejs round-trip.js
155 }
156 = r.stdout
157 assert [REAL_JOINED_SPACE === r.stdout]
158}
159
160proc test-round-trip-ninja-shell {
161 # TODO: our test framework needs a temp dir
162 var dir = '_tmp/quote-test'
163 mkdir -p $dir
164 cd $dir
165
166 var parts = []
167 for s in (REAL_CASES) {
168 var s2 = quote.ninja(s)
169 # now quote it again
170 var s3 = quote.sh(s2)
171 call parts->append(s3)
172 }
173
174 # Here we are testing the shell + Ninja context.
175 # Can we test just the Ninja context? By generating a file with a weird
176 # name?
177
178 echo """
179 rule testrule
180 command = echo $[join(parts, ' ')] > \$out
181 build my-out: testrule my-in
182 """ > build.ninja
183
184 touch my-in
185
186 # We can see the shell invocation
187 # It doesn't preserve newlines
188 # execve("/bin/sh", ["/bin/sh", "-c", "echo 'a' '' 'b' > my-out"]
189
190 #strace -ff ninja my-out
191 ninja my-out
192
193 yb-capture (&r) {
194 # Hm bash says: cannot execute binary file!
195
196 # The \0 byte is not round trippable in /bin/sh
197 # So we should not allow it
198 # We should exclude it
199
200 cat my-out
201 }
202 = r.stdout
203 assert [REAL_JOINED_SPACE === r.stdout]
204}
205
206proc test-round-trip-ninja-only {
207 # TODO: our test framework needs a temp dir
208 var dir = '_tmp/quote-test'
209 mkdir -p $dir
210 cd $dir
211
212 var parts = []
213 for s in (REAL_CASES) {
214 var s2 = quote.ninja(s)
215 call parts->append(s2)
216 }
217
218 # Here we are testing the shell + Ninja context.
219 # Can we test just the Ninja context? By generating a file with a weird
220 # name?
221
222 rm -f --verbose *
223
224 var filename = join(parts, '')
225 echo """
226 rule cp
227 command = cp \$in \$out
228 build $filename: cp my-in
229 default $filename
230 """ > weird-file.ninja
231
232 touch my-in
233
234 # We can see the shell invocation
235 # It doesn't preserve newlines
236 # execve("/bin/sh", ["/bin/sh", "-c", "echo 'a' '' 'b' > my-out"]
237
238 #strace -ff ninja my-out
239 ninja -f weird-file.ninja
240
241 yb-capture (&r) {
242 # python2 doesn't mangle filenames
243 python2 -c '
244import os
245files = [n for n in os.listdir(".") if n not in ["weird-file.ninja", "my-in", ".ninja_log"]]
246print(files[0])
247 '
248 }
249 = r.stdout
250 assert [REAL_JOINED === r.stdout]
251}
252
253
254
255# Note: the \\ is hard to read - '' doesn't help
256const sq_expected = "''\\'''"
257
258# Gah this doesn't work! Because it would require '''
259const sq_expected2 = r'''
260''\'' # Gah this would require three
261'''
262
263# TODO: Test the alphabet encoded to
264#
265# - sh: byte strings without nulls
266# - shell: byte strings without nulls, newlines!
267# - or UTF-8 without nulls?
268# - JSON: encoded form must be UTF-8, with ASCII option \u00ff
269# - HTML: UTF-8, with ASCII option with ÿ
270# - without NULLs
271# - CSV: UTF-8
272# - Python/C: UTF-8 without nulls, with ASCII option
273# - urlParam: ASCII
274#
275# Test what is round-tripped - e.g. 0x00 or 0xff omitted
276
277if is-main {
278 byo-maybe-run
279}
280