| 296 | @unittest.skipIf(sys.platform == 'win32', |
| 297 | 'Windows has a native unicode API') |
| 298 | def test_invalid_utf8_arg(self): |
| 299 | # bpo-35883: Py_DecodeLocale() must escape b'\xfd\xbf\xbf\xbb\xba\xba' |
| 300 | # byte sequence with surrogateescape rather than decoding it as the |
| 301 | # U+7fffbeba character which is outside the [U+0000; U+10ffff] range of |
| 302 | # Python Unicode characters. |
| 303 | # |
| 304 | # Test with default config, in the C locale, in the Python UTF-8 Mode. |
| 305 | code = 'import sys, os; s=os.fsencode(sys.argv[1]); print(ascii(s))' |
| 306 | |
| 307 | def run_default(arg): |
| 308 | cmd = [sys.executable, '-c', code, arg] |
| 309 | return subprocess.run(cmd, stdout=subprocess.PIPE, text=True) |
| 310 | |
| 311 | def run_c_locale(arg): |
| 312 | cmd = [sys.executable, '-c', code, arg] |
| 313 | env = dict(os.environ) |
| 314 | env['LC_ALL'] = 'C' |
| 315 | return subprocess.run(cmd, stdout=subprocess.PIPE, |
| 316 | text=True, env=env) |
| 317 | |
| 318 | def run_utf8_mode(arg): |
| 319 | cmd = [sys.executable, '-X', 'utf8', '-c', code, arg] |
| 320 | return subprocess.run(cmd, stdout=subprocess.PIPE, text=True) |
| 321 | |
| 322 | def run_no_utf8_mode(arg): |
| 323 | cmd = [sys.executable, '-X', 'utf8=0', '-c', code, arg] |
| 324 | return subprocess.run(cmd, stdout=subprocess.PIPE, text=True) |
| 325 | |
| 326 | valid_utf8 = 'e:\xe9, euro:\u20ac, non-bmp:\U0010ffff'.encode('utf-8') |
| 327 | # invalid UTF-8 byte sequences with a valid UTF-8 sequence |
| 328 | # in the middle. |
| 329 | invalid_utf8 = ( |
| 330 | b'\xff' # invalid byte |
| 331 | b'\xc3\xff' # invalid byte sequence |
| 332 | b'\xc3\xa9' # valid utf-8: U+00E9 character |
| 333 | b'\xed\xa0\x80' # lone surrogate character (invalid) |
| 334 | b'\xfd\xbf\xbf\xbb\xba\xba' # character outside [U+0000; U+10ffff] |
| 335 | ) |
| 336 | test_args = [valid_utf8, invalid_utf8] |
| 337 | |
| 338 | for run_cmd in (run_default, run_c_locale, run_utf8_mode, |
| 339 | run_no_utf8_mode): |
| 340 | with self.subTest(run_cmd=run_cmd): |
| 341 | for arg in test_args: |
| 342 | proc = run_cmd(arg) |
| 343 | self.assertEqual(proc.stdout.rstrip(), ascii(arg)) |
| 344 | |
| 345 | @unittest.skipUnless((sys.platform == 'darwin' or |
| 346 | support.is_android), 'test specific to Mac OS X and Android') |