Skip to content

bpo-31904: Add subprocess module support for VxWorks RTOS #12157

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

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Doc/library/subprocess.rst
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,8 @@ functions.

Popen(['/bin/sh', '-c', args[0], args[1], ...])

VxWorks does not support shell argument.

On Windows with ``shell=True``, the :envvar:`COMSPEC` environment variable
specifies the default shell. The only time you need to specify
``shell=True`` on Windows is when the command you wish to execute is built
Expand Down Expand Up @@ -449,6 +451,8 @@ functions.
child process just before the child is executed.
(POSIX only)

VxWorks does not support preexec_fn argument.

.. warning::

The *preexec_fn* parameter is not safe to use in the presence of threads
Expand Down Expand Up @@ -509,6 +513,8 @@ functions.
If *start_new_session* is true the setsid() system call will be made in the
child process prior to the execution of the subprocess. (POSIX only)

VxWorks does not support start_new_session argument.

.. versionchanged:: 3.2
*start_new_session* was added.

Expand Down
29 changes: 26 additions & 3 deletions Lib/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@
_mswindows = True
except ModuleNotFoundError:
_mswindows = False
import _posixsubprocess
_vxworks = (sys.platform == "vxworks")
if _vxworks:
import _vxwapi
else:
import _posixsubprocess
import select
import selectors
else:
Expand Down Expand Up @@ -733,6 +737,14 @@ def __init__(self, args, bufsize=-1, executable=None,
raise ValueError("preexec_fn is not supported on Windows "
"platforms")
else:
if _vxworks:
if shell:
raise ValueError("shell is not supported on VxWorks")
if preexec_fn is not None:
raise ValueError("preexec_fn is not supported on VxWorks")
if start_new_session:
raise ValueError("start_new_session is not supported"
"on VxWorks")
# POSIX
if pass_fds and not close_fds:
warnings.warn("pass_fds overriding close_fds.", RuntimeWarning)
Expand Down Expand Up @@ -1580,15 +1592,26 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
for dir in os.get_exec_path(env))
fds_to_keep = set(pass_fds)
fds_to_keep.add(errpipe_write)
self.pid = _posixsubprocess.fork_exec(
if _vxworks:
self.pid = _vxwapi.rtp_spawn(
args, executable_list,
close_fds, tuple(sorted(map(int, fds_to_keep))),
cwd, env_list,
p2cread, p2cwrite, c2pread, c2pwrite,
errread, errwrite,
errpipe_read, errpipe_write)
if self.pid != -1:
self._child_created = True
else:
self.pid = _posixsubprocess.fork_exec(
args, executable_list,
close_fds, tuple(sorted(map(int, fds_to_keep))),
cwd, env_list,
p2cread, p2cwrite, c2pread, c2pwrite,
errread, errwrite,
errpipe_read, errpipe_write,
restore_signals, start_new_session, preexec_fn)
self._child_created = True
self._child_created = True
finally:
# be sure the FD is closed no matter what
os.close(errpipe_write)
Expand Down
63 changes: 50 additions & 13 deletions Lib/test/test_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@
raise unittest.SkipTest("test is not helpful for PGO")

mswindows = (sys.platform == "win32")
vxworks = (sys.platform == "vxworks")

no_shell = False
no_preexec_fn = False

if vxworks:
mock_fork_exec_or_spawn_fn_name = "subprocess._vxwapi.rtp_spawn"
platform_specific_fork_exec_or_spawn = subprocess._vxwapi.rtp_spawn

no_shell = True
no_preexec_fn = True
else:
mock_fork_exec_or_spawn_fn_name = "subprocess._posixsubprocess.fork_exec"
if not mswindows:
platform_specific_fork_exec_or_spawn = subprocess._posixsubprocess.fork_exec

#
# Depends on the following external programs: Python
Expand Down Expand Up @@ -315,6 +330,7 @@ def test_executable_takes_precedence(self):
executable=NONEXISTING_CMD[0])

@unittest.skipIf(mswindows, "executable argument replaces shell")
@unittest.skipIf(no_shell, "shell argument is not supported")
def test_executable_replaces_shell(self):
# Check that the executable argument replaces the default shell
# when shell=True.
Expand Down Expand Up @@ -1581,8 +1597,8 @@ class PopenNoDestructor(subprocess.Popen):
def __del__(self):
pass

@mock.patch("subprocess._posixsubprocess.fork_exec")
def test_exception_errpipe_normal(self, fork_exec):
@mock.patch(mock_fork_exec_or_spawn_fn_name)
def test_exception_errpipe_normal(self, platform_specific_fork_exec_or_spawn):
"""Test error passing done through errpipe_write in the good case"""
def proper_error(*args):
errpipe_write = args[13]
Expand All @@ -1591,15 +1607,15 @@ def proper_error(*args):
os.write(errpipe_write, b"OSError:" + err_code + b":")
return 0

fork_exec.side_effect = proper_error
platform_specific_fork_exec_or_spawn.side_effect = proper_error

with mock.patch("subprocess.os.waitpid",
side_effect=ChildProcessError):
with self.assertRaises(IsADirectoryError):
self.PopenNoDestructor(["non_existent_command"])

@mock.patch("subprocess._posixsubprocess.fork_exec")
def test_exception_errpipe_bad_data(self, fork_exec):
@mock.patch(mock_fork_exec_or_spawn_fn_name)
def test_exception_errpipe_bad_data(self, platform_specific_fork_exec_or_spawn):
"""Test error passing done through errpipe_write where its not
in the expected format"""
error_data = b"\xFF\x00\xDE\xAD"
Expand All @@ -1611,7 +1627,7 @@ def bad_error(*args):
os.write(errpipe_write, error_data)
return 0

fork_exec.side_effect = bad_error
platform_specific_fork_exec_or_spawn.side_effect = bad_error

with mock.patch("subprocess.os.waitpid",
side_effect=ChildProcessError):
Expand Down Expand Up @@ -1644,6 +1660,8 @@ def test_restore_signals(self):
msg="restore_signals=True should've unblocked "
"SIGPIPE and friends.")

@unittest.skipUnless(os is not None and hasattr(os, 'setsid'),
'need os.setsid')
def test_start_new_session(self):
# For code coverage of calling setsid(). We don't care if we get an
# EPERM error from it depending on the test execution environment, that
Expand Down Expand Up @@ -1690,6 +1708,7 @@ def test_CalledProcessError_str_non_zero(self):
error_string = str(err)
self.assertIn("non-zero exit status 2.", error_string)

@unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported")
def test_preexec(self):
# DISCLAIMER: Setting environment variables is *not* a good use
# of a preexec_fn. This is merely a test.
Expand All @@ -1701,6 +1720,7 @@ def test_preexec(self):
with p:
self.assertEqual(p.stdout.read(), b"apple")

@unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported")
def test_preexec_exception(self):
def raise_it():
raise ValueError("What if two swallows carried a coconut?")
Expand Down Expand Up @@ -1743,6 +1763,7 @@ def _execute_child(self, *args, **kwargs):
os.close(fd)

@unittest.skipIf(not os.path.exists("/dev/zero"), "/dev/zero required.")
@unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported")
def test_preexec_errpipe_does_not_double_close_pipes(self):
"""Issue16140: Don't double close pipes on preexec error."""

Expand All @@ -1756,6 +1777,7 @@ def raise_it():
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, preexec_fn=raise_it)

@unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported")
def test_preexec_gc_module_failure(self):
# This tests the code that disables garbage collection if the child
# process will execute any Python.
Expand Down Expand Up @@ -1795,6 +1817,7 @@ def raise_runtime_error():

@unittest.skipIf(
sys.platform == 'darwin', 'setrlimit() seems to fail on OS X')
@unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported")
def test_preexec_fork_failure(self):
# The internal code did not preserve the previous exception when
# re-enabling garbage collection
Expand Down Expand Up @@ -1840,6 +1863,7 @@ def test_invalid_args(self):
"import sys; sys.exit(47)"],
creationflags=47)

@unittest.skipIf(no_shell, "shell argument is not supported")
def test_shell_sequence(self):
# Run command through the shell (sequence)
newenv = os.environ.copy()
Expand All @@ -1850,6 +1874,7 @@ def test_shell_sequence(self):
with p:
self.assertEqual(p.stdout.read().strip(b" \t\r\n\f"), b"apple")

@unittest.skipIf(no_shell, "shell argument is not supported")
def test_shell_string(self):
# Run command through the shell (string)
newenv = os.environ.copy()
Expand All @@ -1873,6 +1898,7 @@ def test_call_string(self):
os.remove(fname)
self.assertEqual(rc, 47)

@unittest.skipIf(no_shell, "shell argument is not supported")
def test_specific_shell(self):
# Issue #9265: Incorrect name passed as arg[0].
shells = []
Expand Down Expand Up @@ -2203,6 +2229,7 @@ def test_swap_std_fds_with_one_closed(self):
for to_fds in itertools.permutations(range(3), 2):
self._check_swap_std_fds_with_one_closed(from_fds, to_fds)

@unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported")
def test_surrogates_error_message(self):
def prepare():
raise ValueError("surrogate:\uDCff")
Expand Down Expand Up @@ -2251,6 +2278,7 @@ def test_undecodable_env(self):
stdout = stdout.rstrip(b'\n\r')
self.assertEqual(stdout.decode('ascii'), ascii(encoded_value))

@unittest.skipIf(no_shell, "shell argument is not supported")
def test_bytes_program(self):
abs_program = os.fsencode(sys.executable)
path, program = os.path.split(sys.executable)
Expand Down Expand Up @@ -2662,6 +2690,7 @@ def test_leak_fast_process_del_killed(self):
self.assertRaises(OSError, os.waitpid, pid, 0)
self.assertNotIn(ident, [id(o) for o in subprocess._active])

@unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported")
def test_close_fds_after_preexec(self):
fd_status = support.findfile("fd_status.py", subdir="subprocessdata")

Expand All @@ -2680,6 +2709,7 @@ def test_close_fds_after_preexec(self):
self.assertNotIn(fd, remaining_fds)

@support.cpython_only
@unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported")
def test_fork_exec(self):
# Issue #22290: fork_exec() must not crash on memory allocation failure
# or other errors
Expand Down Expand Up @@ -2712,7 +2742,6 @@ def test_fork_exec(self):
@support.cpython_only
def test_fork_exec_sorted_fd_sanity_check(self):
# Issue #23564: sanity check the fork_exec() fds_to_keep sanity check.
import _posixsubprocess
class BadInt:
first = True
def __init__(self, value):
Expand All @@ -2738,12 +2767,19 @@ def __int__(self):
with self.assertRaises(
ValueError,
msg='fds_to_keep={}'.format(fds_to_keep)) as c:
_posixsubprocess.fork_exec(
[b"false"], [b"false"],
True, fds_to_keep, None, [b"env"],
-1, -1, -1, -1,
1, 2, 3, 4,
True, True, None)
if vxworks:
platform_specific_fork_exec_or_spawn(
[b"false"], [b"false"],
True, fds_to_keep, None, [b"env"],
-1, -1, -1, -1,
1, 2, 3, 4)
else:
platform_specific_fork_exec_or_spawn(
[b"false"], [b"false"],
True, fds_to_keep, None, [b"env"],
-1, -1, -1, -1,
1, 2, 3, 4,
True, True, None)
self.assertIn('fds_to_keep', str(c.exception))
finally:
if not gc_enabled:
Expand Down Expand Up @@ -3134,6 +3170,7 @@ def popen_via_context_manager(*args, **kwargs):
raise KeyboardInterrupt # Test how __exit__ handles ^C.
self._test_keyboardinterrupt_no_kill(popen_via_context_manager)

@unittest.skipIf(no_shell, "shell argument is not supported")
def test_getoutput(self):
self.assertEqual(subprocess.getoutput('echo xyzzy'), 'xyzzy')
self.assertEqual(subprocess.getstatusoutput('echo xyzzy'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support subprocess module on VxWorks.
Loading