-
-
Notifications
You must be signed in to change notification settings - Fork 30.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-114272: fix Windows test_asyncio/test_subprocess
when sys.executable
contains unescaped spaces
#128160
base: main
Are you sure you want to change the base?
gh-114272: fix Windows test_asyncio/test_subprocess
when sys.executable
contains unescaped spaces
#128160
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,12 +25,26 @@ | |
if support.check_sanitizer(address=True): | ||
raise unittest.SkipTest("Exposes ASAN flakiness in GitHub CI") | ||
|
||
|
||
def get_quoted_sys_executable(): | ||
if hasattr(get_quoted_sys_executable, '_cached_get_quoted_sys_executable'): | ||
return get_quoted_sys_executable._cached_get_quoted_sys_executable | ||
|
||
sysExecutable = sys.executable | ||
if not ((sysExecutable.startswith('"') and sysExecutable.endswith('"')) or | ||
(sysExecutable.startswith("'") and sysExecutable.endswith("'"))): | ||
if ' ' in (sysExecutable if sys.platform == 'win32' else sysExecutable.replace('\\ ', '')): | ||
Comment on lines
+34
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we use some smarter escape mechanism? for instance, something in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. os.path does not add the quotes and shlex.quote quotes a quoted string ad-nauseam. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. *fewer failed test There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cc @zooba for how we should proceed here. |
||
sysExecutable = f'"{sysExecutable}"' | ||
|
||
get_quoted_sys_executable._cached_get_quoted_sys_executable = sysExecutable | ||
return sysExecutable | ||
|
||
# Program blocking | ||
PROGRAM_BLOCKED = [sys.executable, '-c', 'import time; time.sleep(3600)'] | ||
PROGRAM_BLOCKED = [get_quoted_sys_executable(), '-c', 'import time; time.sleep(3600)'] | ||
|
||
# Program copying input to output | ||
PROGRAM_CAT = [ | ||
sys.executable, '-c', | ||
get_quoted_sys_executable(), '-c', | ||
';'.join(('import sys', | ||
'data = sys.stdin.buffer.read()', | ||
'sys.stdout.buffer.write(data)'))] | ||
|
@@ -209,7 +223,7 @@ def test_kill(self): | |
|
||
def test_kill_issue43884(self): | ||
if sys.platform == 'win32': | ||
blocking_shell_command = f'"{sys.executable}" -c "import time; time.sleep(2)"' | ||
blocking_shell_command = f'"{get_quoted_sys_executable()}" -c "import time; time.sleep(2)"' | ||
else: | ||
blocking_shell_command = 'sleep 1; sleep 1' | ||
creationflags = 0 | ||
|
@@ -255,7 +269,7 @@ def test_send_signal(self): | |
old_handler = signal.signal(signal.SIGHUP, signal.SIG_DFL) | ||
try: | ||
code = 'import time; print("sleeping", flush=True); time.sleep(3600)' | ||
args = [sys.executable, '-c', code] | ||
args = [get_quoted_sys_executable(), '-c', code] | ||
proc = self.loop.run_until_complete( | ||
asyncio.create_subprocess_exec( | ||
*args, | ||
|
@@ -304,7 +318,7 @@ def test_stdin_broken_pipe(self): | |
# the program ends before the stdin can be fed | ||
proc = self.loop.run_until_complete( | ||
asyncio.create_subprocess_exec( | ||
sys.executable, '-c', code, | ||
get_quoted_sys_executable(), '-c', code, | ||
stdin=subprocess.PIPE, | ||
**kwargs | ||
) | ||
|
@@ -330,7 +344,7 @@ def test_communicate_ignore_broken_pipe(self): | |
# the program ends before the stdin can be fed | ||
proc = self.loop.run_until_complete( | ||
asyncio.create_subprocess_exec( | ||
sys.executable, '-c', 'pass', | ||
get_quoted_sys_executable(), '-c', 'pass', | ||
stdin=subprocess.PIPE, | ||
) | ||
) | ||
|
@@ -362,7 +376,7 @@ async def connect_read_pipe_mock(*args, **kw): | |
self.loop.connect_read_pipe = connect_read_pipe_mock | ||
|
||
proc = await asyncio.create_subprocess_exec( | ||
sys.executable, '-c', code, | ||
get_quoted_sys_executable(), '-c', code, | ||
stdin=asyncio.subprocess.PIPE, | ||
stdout=asyncio.subprocess.PIPE, | ||
limit=limit, | ||
|
@@ -390,7 +404,7 @@ def test_stdin_not_inheritable(self): | |
async def len_message(message): | ||
code = 'import sys; data = sys.stdin.read(); print(len(data))' | ||
proc = await asyncio.create_subprocess_exec( | ||
sys.executable, '-c', code, | ||
get_quoted_sys_executable(), '-c', code, | ||
stdin=asyncio.subprocess.PIPE, | ||
stdout=asyncio.subprocess.PIPE, | ||
stderr=asyncio.subprocess.PIPE, | ||
|
@@ -409,7 +423,7 @@ def test_empty_input(self): | |
async def empty_input(): | ||
code = 'import sys; data = sys.stdin.read(); print(len(data))' | ||
proc = await asyncio.create_subprocess_exec( | ||
sys.executable, '-c', code, | ||
get_quoted_sys_executable(), '-c', code, | ||
stdin=asyncio.subprocess.PIPE, | ||
stdout=asyncio.subprocess.PIPE, | ||
stderr=asyncio.subprocess.PIPE, | ||
|
@@ -428,7 +442,7 @@ def test_devnull_input(self): | |
async def empty_input(): | ||
code = 'import sys; data = sys.stdin.read(); print(len(data))' | ||
proc = await asyncio.create_subprocess_exec( | ||
sys.executable, '-c', code, | ||
get_quoted_sys_executable(), '-c', code, | ||
stdin=asyncio.subprocess.DEVNULL, | ||
stdout=asyncio.subprocess.PIPE, | ||
stderr=asyncio.subprocess.PIPE, | ||
|
@@ -447,7 +461,7 @@ def test_devnull_output(self): | |
async def empty_output(): | ||
code = 'import sys; data = sys.stdin.read(); print(len(data))' | ||
proc = await asyncio.create_subprocess_exec( | ||
sys.executable, '-c', code, | ||
get_quoted_sys_executable(), '-c', code, | ||
stdin=asyncio.subprocess.PIPE, | ||
stdout=asyncio.subprocess.DEVNULL, | ||
stderr=asyncio.subprocess.PIPE, | ||
|
@@ -466,7 +480,7 @@ def test_devnull_error(self): | |
async def empty_error(): | ||
code = 'import sys; data = sys.stdin.read(); print(len(data))' | ||
proc = await asyncio.create_subprocess_exec( | ||
sys.executable, '-c', code, | ||
get_quoted_sys_executable(), '-c', code, | ||
stdin=asyncio.subprocess.PIPE, | ||
stdout=asyncio.subprocess.PIPE, | ||
stderr=asyncio.subprocess.DEVNULL, | ||
|
@@ -487,7 +501,7 @@ def test_devstdin_input(self): | |
async def devstdin_input(message): | ||
code = 'file = open("/dev/stdin"); data = file.read(); print(len(data))' | ||
proc = await asyncio.create_subprocess_exec( | ||
sys.executable, '-c', code, | ||
get_quoted_sys_executable(), '-c', code, | ||
stdin=asyncio.subprocess.PIPE, | ||
stdout=asyncio.subprocess.PIPE, | ||
stderr=asyncio.subprocess.PIPE, | ||
|
@@ -643,7 +657,7 @@ async def _test_popen_error(self, stdin): | |
with warnings.catch_warnings(record=True) as warns: | ||
with self.assertRaises(exc): | ||
await asyncio.create_subprocess_exec( | ||
sys.executable, | ||
get_quoted_sys_executable(), | ||
'-c', | ||
'pass', | ||
stdin=stdin | ||
|
@@ -671,7 +685,7 @@ async def execute(): | |
'sys.exit(1)']) | ||
|
||
process = await asyncio.create_subprocess_exec( | ||
sys.executable, '-c', code, | ||
get_quoted_sys_executable(), '-c', code, | ||
stdout=asyncio.subprocess.PIPE, | ||
) | ||
|
||
|
@@ -687,15 +701,15 @@ async def execute(): | |
def test_create_subprocess_exec_text_mode_fails(self): | ||
async def execute(): | ||
with self.assertRaises(ValueError): | ||
await subprocess.create_subprocess_exec(sys.executable, | ||
await subprocess.create_subprocess_exec(get_quoted_sys_executable(), | ||
text=True) | ||
|
||
with self.assertRaises(ValueError): | ||
await subprocess.create_subprocess_exec(sys.executable, | ||
await subprocess.create_subprocess_exec(get_quoted_sys_executable(), | ||
encoding="utf-8") | ||
|
||
with self.assertRaises(ValueError): | ||
await subprocess.create_subprocess_exec(sys.executable, | ||
await subprocess.create_subprocess_exec(get_quoted_sys_executable(), | ||
errors="strict") | ||
|
||
self.loop.run_until_complete(execute()) | ||
|
@@ -704,26 +718,26 @@ def test_create_subprocess_shell_text_mode_fails(self): | |
|
||
async def execute(): | ||
with self.assertRaises(ValueError): | ||
await subprocess.create_subprocess_shell(sys.executable, | ||
await subprocess.create_subprocess_shell(get_quoted_sys_executable(), | ||
text=True) | ||
|
||
with self.assertRaises(ValueError): | ||
await subprocess.create_subprocess_shell(sys.executable, | ||
await subprocess.create_subprocess_shell(get_quoted_sys_executable(), | ||
encoding="utf-8") | ||
|
||
with self.assertRaises(ValueError): | ||
await subprocess.create_subprocess_shell(sys.executable, | ||
await subprocess.create_subprocess_shell(get_quoted_sys_executable(), | ||
errors="strict") | ||
|
||
self.loop.run_until_complete(execute()) | ||
|
||
def test_create_subprocess_exec_with_path(self): | ||
async def execute(): | ||
p = await subprocess.create_subprocess_exec( | ||
os_helper.FakePath(sys.executable), '-c', 'pass') | ||
os_helper.FakePath(get_quoted_sys_executable()), '-c', 'pass') | ||
await p.wait() | ||
p = await subprocess.create_subprocess_exec( | ||
sys.executable, '-c', 'pass', os_helper.FakePath('.')) | ||
get_quoted_sys_executable(), '-c', 'pass', os_helper.FakePath('.')) | ||
await p.wait() | ||
|
||
self.assertIsNone(self.loop.run_until_complete(execute())) | ||
|
@@ -739,7 +753,7 @@ async def check_stdout_output(self, coro, output): | |
|
||
def test_create_subprocess_env_shell(self) -> None: | ||
async def main() -> None: | ||
executable = sys.executable | ||
executable = get_quoted_sys_executable() | ||
if sys.platform == "win32": | ||
executable = f'"{executable}"' | ||
cmd = f'''{executable} -c "import os, sys; sys.stdout.write(os.getenv('FOO'))"''' | ||
|
@@ -754,7 +768,7 @@ async def main() -> None: | |
|
||
def test_create_subprocess_env_exec(self) -> None: | ||
async def main() -> None: | ||
cmd = [sys.executable, "-c", | ||
cmd = [get_quoted_sys_executable(), "-c", | ||
"import os, sys; sys.stdout.write(os.getenv('FOO'))"] | ||
env = os.environ.copy() | ||
env["FOO"] = "baz" | ||
|
@@ -825,7 +839,7 @@ async def main() -> None: | |
exit_future = asyncio.Future() | ||
code = 'import sys; sys.stdout.write("stdout"); sys.stderr.write("stderr")' | ||
transport, _ = await loop.subprocess_exec(lambda: MyProtocol(exit_future), | ||
sys.executable, '-c', code, stdin=None) | ||
get_quoted_sys_executable(), '-c', code, stdin=None) | ||
await exit_future | ||
transport.close() | ||
|
||
|
@@ -858,7 +872,7 @@ async def get_command_stdout(cmd, *args): | |
|
||
async def main(): | ||
outputs = [f'foo{i}' for i in range(10)] | ||
res = await asyncio.gather(*[get_command_stdout(sys.executable, '-c', | ||
res = await asyncio.gather(*[get_command_stdout(get_quoted_sys_executable(), '-c', | ||
f'print({out!r})') for out in outputs]) | ||
self.assertEqual(res, outputs) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you refactor the function and use shorter names for the attributes? it's a test file so it doesn't matter what we use. Or use an LRU-cache instead. OTOH, you can setup the test cases so that they have a test attribute (namely
cls.sysexec = ...
insetUpClass
and you'd use asself.sysexec
instead ofsys.executable
in the tests)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi picnixz.
I think caching is overkill. I'd do it for my own code but not for opensource to keep it simple. I'll remove it.