| 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 |
|