Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 92b8322

Browse files
nanjekyejoannahvstinner
authored andcommitted
bpo-35674: Add os.posix_spawnp() (pythonGH-11554)
Add a new os.posix_spawnp() function.
1 parent 9daecf3 commit 92b8322

File tree

9 files changed

+322
-112
lines changed

9 files changed

+322
-112
lines changed

Doc/library/os.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3396,6 +3396,10 @@ written in Python, such as a mail server's external command delivery program.
33963396
The positional-only arguments *path*, *args*, and *env* are similar to
33973397
:func:`execve`.
33983398

3399+
The *path* parameter is the path to the executable file.The *path* should
3400+
contain a directory.Use :func:`posix_spawnp` to pass an executable file
3401+
without directory.
3402+
33993403
The *file_actions* argument may be a sequence of tuples describing actions
34003404
to take on specific file descriptors in the child process between the C
34013405
library implementation's :c:func:`fork` and :c:func:`exec` steps.
@@ -3459,6 +3463,19 @@ written in Python, such as a mail server's external command delivery program.
34593463
.. versionadded:: 3.7
34603464

34613465

3466+
.. function:: posix_spawnp(path, argv, env, *, file_actions=None, \
3467+
setpgroup=None, resetids=False, setsigmask=(), \
3468+
setsigdef=(), scheduler=None)
3469+
3470+
Wraps the :c:func:`posix_spawnp` C library API for use from Python.
3471+
3472+
Similar to :func:`posix_spawn` except that the system searches
3473+
for the *executable* file in the list of directories specified by the
3474+
:envvar:`PATH` environment variable (in the same way as for ``execvp(3)``).
3475+
3476+
.. versionadded:: 3.8
3477+
3478+
34623479
.. function:: register_at_fork(*, before=None, after_in_parent=None, \
34633480
after_in_child=None)
34643481

Lib/test/test_posix.py

Lines changed: 119 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,10 +1489,10 @@ def test_setgroups(self):
14891489
self.assertListEqual(groups, posix.getgroups())
14901490

14911491

1492-
@unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
1493-
class TestPosixSpawn(unittest.TestCase):
1494-
# Program which does nothing and exit with status 0 (success)
1492+
class _PosixSpawnMixin:
1493+
# Program which does nothing and exits with status 0 (success)
14951494
NOOP_PROGRAM = (sys.executable, '-I', '-S', '-c', 'pass')
1495+
spawn_func = None
14961496

14971497
def python_args(self, *args):
14981498
# Disable site module to avoid side effects. For example,
@@ -1511,17 +1511,17 @@ def test_returns_pid(self):
15111511
pidfile.write(str(os.getpid()))
15121512
"""
15131513
args = self.python_args('-c', script)
1514-
pid = posix.posix_spawn(args[0], args, os.environ)
1514+
pid = self.spawn_func(args[0], args, os.environ)
15151515
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
15161516
with open(pidfile) as f:
15171517
self.assertEqual(f.read(), str(pid))
15181518

15191519
def test_no_such_executable(self):
15201520
no_such_executable = 'no_such_executable'
15211521
try:
1522-
pid = posix.posix_spawn(no_such_executable,
1523-
[no_such_executable],
1524-
os.environ)
1522+
pid = self.spawn_func(no_such_executable,
1523+
[no_such_executable],
1524+
os.environ)
15251525
except FileNotFoundError as exc:
15261526
self.assertEqual(exc.filename, no_such_executable)
15271527
else:
@@ -1538,14 +1538,14 @@ def test_specify_environment(self):
15381538
envfile.write(os.environ['foo'])
15391539
"""
15401540
args = self.python_args('-c', script)
1541-
pid = posix.posix_spawn(args[0], args,
1542-
{**os.environ, 'foo': 'bar'})
1541+
pid = self.spawn_func(args[0], args,
1542+
{**os.environ, 'foo': 'bar'})
15431543
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
15441544
with open(envfile) as f:
15451545
self.assertEqual(f.read(), 'bar')
15461546

15471547
def test_empty_file_actions(self):
1548-
pid = posix.posix_spawn(
1548+
pid = self.spawn_func(
15491549
self.NOOP_PROGRAM[0],
15501550
self.NOOP_PROGRAM,
15511551
os.environ,
@@ -1554,7 +1554,7 @@ def test_empty_file_actions(self):
15541554
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
15551555

15561556
def test_resetids_explicit_default(self):
1557-
pid = posix.posix_spawn(
1557+
pid = self.spawn_func(
15581558
sys.executable,
15591559
[sys.executable, '-c', 'pass'],
15601560
os.environ,
@@ -1563,7 +1563,7 @@ def test_resetids_explicit_default(self):
15631563
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
15641564

15651565
def test_resetids(self):
1566-
pid = posix.posix_spawn(
1566+
pid = self.spawn_func(
15671567
sys.executable,
15681568
[sys.executable, '-c', 'pass'],
15691569
os.environ,
@@ -1573,12 +1573,12 @@ def test_resetids(self):
15731573

15741574
def test_resetids_wrong_type(self):
15751575
with self.assertRaises(TypeError):
1576-
posix.posix_spawn(sys.executable,
1577-
[sys.executable, "-c", "pass"],
1578-
os.environ, resetids=None)
1576+
self.spawn_func(sys.executable,
1577+
[sys.executable, "-c", "pass"],
1578+
os.environ, resetids=None)
15791579

15801580
def test_setpgroup(self):
1581-
pid = posix.posix_spawn(
1581+
pid = self.spawn_func(
15821582
sys.executable,
15831583
[sys.executable, '-c', 'pass'],
15841584
os.environ,
@@ -1588,9 +1588,9 @@ def test_setpgroup(self):
15881588

15891589
def test_setpgroup_wrong_type(self):
15901590
with self.assertRaises(TypeError):
1591-
posix.posix_spawn(sys.executable,
1592-
[sys.executable, "-c", "pass"],
1593-
os.environ, setpgroup="023")
1591+
self.spawn_func(sys.executable,
1592+
[sys.executable, "-c", "pass"],
1593+
os.environ, setpgroup="023")
15941594

15951595
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
15961596
'need signal.pthread_sigmask()')
@@ -1599,7 +1599,7 @@ def test_setsigmask(self):
15991599
import signal
16001600
signal.raise_signal(signal.SIGUSR1)""")
16011601

1602-
pid = posix.posix_spawn(
1602+
pid = self.spawn_func(
16031603
sys.executable,
16041604
[sys.executable, '-c', code],
16051605
os.environ,
@@ -1609,18 +1609,18 @@ def test_setsigmask(self):
16091609

16101610
def test_setsigmask_wrong_type(self):
16111611
with self.assertRaises(TypeError):
1612-
posix.posix_spawn(sys.executable,
1613-
[sys.executable, "-c", "pass"],
1614-
os.environ, setsigmask=34)
1612+
self.spawn_func(sys.executable,
1613+
[sys.executable, "-c", "pass"],
1614+
os.environ, setsigmask=34)
16151615
with self.assertRaises(TypeError):
1616-
posix.posix_spawn(sys.executable,
1617-
[sys.executable, "-c", "pass"],
1618-
os.environ, setsigmask=["j"])
1616+
self.spawn_func(sys.executable,
1617+
[sys.executable, "-c", "pass"],
1618+
os.environ, setsigmask=["j"])
16191619
with self.assertRaises(ValueError):
1620-
posix.posix_spawn(sys.executable,
1621-
[sys.executable, "-c", "pass"],
1622-
os.environ, setsigmask=[signal.NSIG,
1623-
signal.NSIG+1])
1620+
self.spawn_func(sys.executable,
1621+
[sys.executable, "-c", "pass"],
1622+
os.environ, setsigmask=[signal.NSIG,
1623+
signal.NSIG+1])
16241624

16251625
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
16261626
'need signal.pthread_sigmask()')
@@ -1630,7 +1630,7 @@ def test_setsigdef(self):
16301630
import signal
16311631
signal.raise_signal(signal.SIGUSR1)""")
16321632
try:
1633-
pid = posix.posix_spawn(
1633+
pid = self.spawn_func(
16341634
sys.executable,
16351635
[sys.executable, '-c', code],
16361636
os.environ,
@@ -1646,17 +1646,17 @@ def test_setsigdef(self):
16461646

16471647
def test_setsigdef_wrong_type(self):
16481648
with self.assertRaises(TypeError):
1649-
posix.posix_spawn(sys.executable,
1650-
[sys.executable, "-c", "pass"],
1651-
os.environ, setsigdef=34)
1649+
self.spawn_func(sys.executable,
1650+
[sys.executable, "-c", "pass"],
1651+
os.environ, setsigdef=34)
16521652
with self.assertRaises(TypeError):
1653-
posix.posix_spawn(sys.executable,
1654-
[sys.executable, "-c", "pass"],
1655-
os.environ, setsigdef=["j"])
1653+
self.spawn_func(sys.executable,
1654+
[sys.executable, "-c", "pass"],
1655+
os.environ, setsigdef=["j"])
16561656
with self.assertRaises(ValueError):
1657-
posix.posix_spawn(sys.executable,
1658-
[sys.executable, "-c", "pass"],
1659-
os.environ, setsigdef=[signal.NSIG, signal.NSIG+1])
1657+
self.spawn_func(sys.executable,
1658+
[sys.executable, "-c", "pass"],
1659+
os.environ, setsigdef=[signal.NSIG, signal.NSIG+1])
16601660

16611661
@requires_sched
16621662
@unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')),
@@ -1670,7 +1670,7 @@ def test_setscheduler_only_param(self):
16701670
sys.exit(101)
16711671
if os.sched_getparam(0).sched_priority != {priority}:
16721672
sys.exit(102)""")
1673-
pid = posix.posix_spawn(
1673+
pid = self.spawn_func(
16741674
sys.executable,
16751675
[sys.executable, '-c', code],
16761676
os.environ,
@@ -1690,7 +1690,7 @@ def test_setscheduler_with_policy(self):
16901690
sys.exit(101)
16911691
if os.sched_getparam(0).sched_priority != {priority}:
16921692
sys.exit(102)""")
1693-
pid = posix.posix_spawn(
1693+
pid = self.spawn_func(
16941694
sys.executable,
16951695
[sys.executable, '-c', code],
16961696
os.environ,
@@ -1704,40 +1704,40 @@ def test_multiple_file_actions(self):
17041704
(os.POSIX_SPAWN_CLOSE, 0),
17051705
(os.POSIX_SPAWN_DUP2, 1, 4),
17061706
]
1707-
pid = posix.posix_spawn(self.NOOP_PROGRAM[0],
1708-
self.NOOP_PROGRAM,
1709-
os.environ,
1710-
file_actions=file_actions)
1707+
pid = self.spawn_func(self.NOOP_PROGRAM[0],
1708+
self.NOOP_PROGRAM,
1709+
os.environ,
1710+
file_actions=file_actions)
17111711
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
17121712

17131713
def test_bad_file_actions(self):
17141714
args = self.NOOP_PROGRAM
17151715
with self.assertRaises(TypeError):
1716-
posix.posix_spawn(args[0], args, os.environ,
1717-
file_actions=[None])
1716+
self.spawn_func(args[0], args, os.environ,
1717+
file_actions=[None])
17181718
with self.assertRaises(TypeError):
1719-
posix.posix_spawn(args[0], args, os.environ,
1720-
file_actions=[()])
1719+
self.spawn_func(args[0], args, os.environ,
1720+
file_actions=[()])
17211721
with self.assertRaises(TypeError):
1722-
posix.posix_spawn(args[0], args, os.environ,
1723-
file_actions=[(None,)])
1722+
self.spawn_func(args[0], args, os.environ,
1723+
file_actions=[(None,)])
17241724
with self.assertRaises(TypeError):
1725-
posix.posix_spawn(args[0], args, os.environ,
1726-
file_actions=[(12345,)])
1725+
self.spawn_func(args[0], args, os.environ,
1726+
file_actions=[(12345,)])
17271727
with self.assertRaises(TypeError):
1728-
posix.posix_spawn(args[0], args, os.environ,
1729-
file_actions=[(os.POSIX_SPAWN_CLOSE,)])
1728+
self.spawn_func(args[0], args, os.environ,
1729+
file_actions=[(os.POSIX_SPAWN_CLOSE,)])
17301730
with self.assertRaises(TypeError):
1731-
posix.posix_spawn(args[0], args, os.environ,
1732-
file_actions=[(os.POSIX_SPAWN_CLOSE, 1, 2)])
1731+
self.spawn_func(args[0], args, os.environ,
1732+
file_actions=[(os.POSIX_SPAWN_CLOSE, 1, 2)])
17331733
with self.assertRaises(TypeError):
1734-
posix.posix_spawn(args[0], args, os.environ,
1735-
file_actions=[(os.POSIX_SPAWN_CLOSE, None)])
1734+
self.spawn_func(args[0], args, os.environ,
1735+
file_actions=[(os.POSIX_SPAWN_CLOSE, None)])
17361736
with self.assertRaises(ValueError):
1737-
posix.posix_spawn(args[0], args, os.environ,
1738-
file_actions=[(os.POSIX_SPAWN_OPEN,
1739-
3, __file__ + '\0',
1740-
os.O_RDONLY, 0)])
1737+
self.spawn_func(args[0], args, os.environ,
1738+
file_actions=[(os.POSIX_SPAWN_OPEN,
1739+
3, __file__ + '\0',
1740+
os.O_RDONLY, 0)])
17411741

17421742
def test_open_file(self):
17431743
outfile = support.TESTFN
@@ -1752,8 +1752,8 @@ def test_open_file(self):
17521752
stat.S_IRUSR | stat.S_IWUSR),
17531753
]
17541754
args = self.python_args('-c', script)
1755-
pid = posix.posix_spawn(args[0], args, os.environ,
1756-
file_actions=file_actions)
1755+
pid = self.spawn_func(args[0], args, os.environ,
1756+
file_actions=file_actions)
17571757
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
17581758
with open(outfile) as f:
17591759
self.assertEqual(f.read(), 'hello')
@@ -1770,8 +1770,8 @@ def test_close_file(self):
17701770
closefile.write('is closed %d' % e.errno)
17711771
"""
17721772
args = self.python_args('-c', script)
1773-
pid = posix.posix_spawn(args[0], args, os.environ,
1774-
file_actions=[(os.POSIX_SPAWN_CLOSE, 0),])
1773+
pid = self.spawn_func(args[0], args, os.environ,
1774+
file_actions=[(os.POSIX_SPAWN_CLOSE, 0)])
17751775
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
17761776
with open(closefile) as f:
17771777
self.assertEqual(f.read(), 'is closed %d' % errno.EBADF)
@@ -1788,16 +1788,64 @@ def test_dup2(self):
17881788
(os.POSIX_SPAWN_DUP2, childfile.fileno(), 1),
17891789
]
17901790
args = self.python_args('-c', script)
1791-
pid = posix.posix_spawn(args[0], args, os.environ,
1792-
file_actions=file_actions)
1791+
pid = self.spawn_func(args[0], args, os.environ,
1792+
file_actions=file_actions)
17931793
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
17941794
with open(dupfile) as f:
17951795
self.assertEqual(f.read(), 'hello')
17961796

17971797

1798+
@unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
1799+
class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin):
1800+
spawn_func = getattr(posix, 'posix_spawn', None)
1801+
1802+
1803+
@unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp")
1804+
class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin):
1805+
spawn_func = getattr(posix, 'posix_spawnp', None)
1806+
1807+
@support.skip_unless_symlink
1808+
def test_posix_spawnp(self):
1809+
# Use a symlink to create a program in its own temporary directory
1810+
temp_dir = tempfile.mkdtemp()
1811+
self.addCleanup(support.rmtree, temp_dir)
1812+
1813+
program = 'posix_spawnp_test_program.exe'
1814+
program_fullpath = os.path.join(temp_dir, program)
1815+
os.symlink(sys.executable, program_fullpath)
1816+
1817+
try:
1818+
path = os.pathsep.join((temp_dir, os.environ['PATH']))
1819+
except KeyError:
1820+
path = temp_dir # PATH is not set
1821+
1822+
spawn_args = (program, '-I', '-S', '-c', 'pass')
1823+
code = textwrap.dedent("""
1824+
import os
1825+
args = %a
1826+
pid = os.posix_spawnp(args[0], args, os.environ)
1827+
pid2, status = os.waitpid(pid, 0)
1828+
if pid2 != pid:
1829+
raise Exception(f"pid {pid2} != {pid}")
1830+
if status != 0:
1831+
raise Exception(f"status {status} != 0")
1832+
""" % (spawn_args,))
1833+
1834+
# Use a subprocess to test os.posix_spawnp() with a modified PATH
1835+
# environment variable: posix_spawnp() uses the current environment
1836+
# to locate the program, not its environment argument.
1837+
args = ('-c', code)
1838+
assert_python_ok(*args, PATH=path)
1839+
1840+
17981841
def test_main():
17991842
try:
1800-
support.run_unittest(PosixTester, PosixGroupsTester, TestPosixSpawn)
1843+
support.run_unittest(
1844+
PosixTester,
1845+
PosixGroupsTester,
1846+
TestPosixSpawn,
1847+
TestPosixSpawnP,
1848+
)
18011849
finally:
18021850
support.reap_children()
18031851

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add a new :func:`os.posix_spawnp` function.
2+
Patch by Joannah Nanjekye.

0 commit comments

Comments
 (0)