1 | #!bin/ysh
|
2 |
|
3 | use $LIB_YSH/quote.ysh
|
4 | use $LIB_YSH/yblocks.ysh --pick yb-capture
|
5 |
|
6 | # Change to 'use'?
|
7 | source $LIB_OSH/byo-server.sh
|
8 |
|
9 | proc test-sh {
|
10 | assert ["'x'" === quote.sh('x')]
|
11 | assert [sq_expected === quote.sh("'")]
|
12 | }
|
13 |
|
14 | proc 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 |
|
23 | proc 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 |
|
35 | proc 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 |
|
58 | var 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 |
|
99 | const REAL_JOINED = join(REAL_CASES, '') ++ u'\n'
|
100 | const REAL_JOINED_SPACE = join(REAL_CASES, ' ') ++ u'\n' # for shell
|
101 |
|
102 | proc 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 |
|
128 | proc 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 |
|
160 | proc 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 |
|
206 | proc 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 '
|
244 | import os
|
245 | files = [n for n in os.listdir(".") if n not in ["weird-file.ninja", "my-in", ".ninja_log"]]
|
246 | print(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
|
256 | const sq_expected = "''\\'''"
|
257 |
|
258 | # Gah this doesn't work! Because it would require '''
|
259 | const 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 |
|
277 | if is-main {
|
278 | byo-maybe-run
|
279 | }
|
280 |
|