Skip to content

Commit cb9663c

Browse files
authored
Merge pull request RustPython#3926 from youknowone/unittest
update signal and venv
2 parents 9878d72 + 2e6bc39 commit cb9663c

File tree

10 files changed

+149
-42
lines changed

10 files changed

+149
-42
lines changed

Lib/signal.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import _signal
22
from _signal import *
3-
from functools import wraps as _wraps
43
from enum import IntEnum as _IntEnum
54

65
_globals = globals()
@@ -42,6 +41,16 @@ def _enum_to_int(value):
4241
return value
4342

4443

44+
# Similar to functools.wraps(), but only assign __doc__.
45+
# __module__ should be preserved,
46+
# __name__ and __qualname__ are already fine,
47+
# __annotations__ is not set.
48+
def _wraps(wrapped):
49+
def decorator(wrapper):
50+
wrapper.__doc__ = wrapped.__doc__
51+
return wrapper
52+
return decorator
53+
4554
@_wraps(_signal.signal)
4655
def signal(signalnum, handler):
4756
handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler))
@@ -59,7 +68,6 @@ def getsignal(signalnum):
5968
def pthread_sigmask(how, mask):
6069
sigs_set = _signal.pthread_sigmask(how, mask)
6170
return set(_int_to_enum(x, Signals) for x in sigs_set)
62-
pthread_sigmask.__doc__ = _signal.pthread_sigmask.__doc__
6371

6472

6573
if 'sigpending' in _globals:
@@ -73,7 +81,6 @@ def sigpending():
7381
def sigwait(sigset):
7482
retsig = _signal.sigwait(sigset)
7583
return _int_to_enum(retsig, Signals)
76-
sigwait.__doc__ = _signal.sigwait
7784

7885

7986
if 'valid_signals' in _globals:

Lib/test/test_signal.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import errno
2+
import inspect
23
import os
34
import random
45
import signal
@@ -33,6 +34,14 @@ def test_enums(self):
3334
self.assertIsInstance(sig, signal.Signals)
3435
self.assertEqual(sys.platform, "win32")
3536

37+
def test_functions_module_attr(self):
38+
# Issue #27718: If __all__ is not defined all non-builtin functions
39+
# should have correct __module__ to be displayed by pydoc.
40+
for name in dir(signal):
41+
value = getattr(signal, name)
42+
if inspect.isroutine(value) and not inspect.isbuiltin(value):
43+
self.assertEqual(value.__module__, 'signal')
44+
3645

3746
@unittest.skipIf(sys.platform == "win32", "Not valid on Windows")
3847
class PosixTests(unittest.TestCase):
@@ -552,16 +561,20 @@ def handler(signum, frame):
552561
else:
553562
write.setblocking(False)
554563
555-
# Start with large chunk size to reduce the
556-
# number of send needed to fill the buffer.
557564
written = 0
558-
for chunk_size in (2 ** 16, 2 ** 8, 1):
565+
if sys.platform == "vxworks":
566+
CHUNK_SIZES = (1,)
567+
else:
568+
# Start with large chunk size to reduce the
569+
# number of send needed to fill the buffer.
570+
CHUNK_SIZES = (2 ** 16, 2 ** 8, 1)
571+
for chunk_size in CHUNK_SIZES:
559572
chunk = b"x" * chunk_size
560573
try:
561574
while True:
562575
write.send(chunk)
563576
written += chunk_size
564-
except (BlockingIOError, socket.timeout):
577+
except (BlockingIOError, TimeoutError):
565578
pass
566579
567580
print(f"%s bytes written into the socketpair" % written, flush=True)
@@ -628,6 +641,7 @@ def handler(signum, frame):
628641

629642

630643
@unittest.skipIf(sys.platform == "win32", "Not valid on Windows")
644+
@unittest.skipUnless(hasattr(signal, 'siginterrupt'), "needs signal.siginterrupt()")
631645
class SiginterruptTest(unittest.TestCase):
632646

633647
def readpipe_interrupted(self, interrupt):
@@ -678,7 +692,7 @@ def handler(signum, frame):
678692
# wait until the child process is loaded and has started
679693
first_line = process.stdout.readline()
680694

681-
stdout, stderr = process.communicate(timeout=5.0)
695+
stdout, stderr = process.communicate(timeout=support.SHORT_TIMEOUT)
682696
except subprocess.TimeoutExpired:
683697
process.kill()
684698
return False
@@ -717,6 +731,8 @@ def test_siginterrupt_off(self):
717731

718732

719733
@unittest.skipIf(sys.platform == "win32", "Not valid on Windows")
734+
@unittest.skipUnless(hasattr(signal, 'getitimer') and hasattr(signal, 'setitimer'),
735+
"needs signal.getitimer() and signal.setitimer()")
720736
class ItimerTest(unittest.TestCase):
721737
def setUp(self):
722738
self.hndl_called = False
@@ -1240,7 +1256,7 @@ def second_handler(signum=None, frame=None):
12401256
self.setsig(signal.SIGALRM, second_handler) # for ITIMER_REAL
12411257

12421258
expected_sigs = 0
1243-
deadline = time.monotonic() + 15.0
1259+
deadline = time.monotonic() + support.SHORT_TIMEOUT
12441260

12451261
while expected_sigs < N:
12461262
os.kill(os.getpid(), signal.SIGPROF)
@@ -1274,7 +1290,7 @@ def handler(signum, frame):
12741290
self.setsig(signal.SIGALRM, handler) # for ITIMER_REAL
12751291

12761292
expected_sigs = 0
1277-
deadline = time.monotonic() + 15.0
1293+
deadline = time.monotonic() + support.SHORT_TIMEOUT
12781294

12791295
while expected_sigs < N:
12801296
# Hopefully the SIGALRM will be received somewhere during
@@ -1336,7 +1352,7 @@ def cycle_handlers():
13361352
# race condition, check it.
13371353
self.assertIsInstance(cm.unraisable.exc_value, OSError)
13381354
self.assertIn(
1339-
f"Signal {signum} ignored due to race condition",
1355+
f"Signal {signum:d} ignored due to race condition",
13401356
str(cm.unraisable.exc_value))
13411357
ignored = True
13421358

@@ -1387,6 +1403,27 @@ def handler(a, b):
13871403
self.assertTrue(is_ok)
13881404

13891405

1406+
class PidfdSignalTest(unittest.TestCase):
1407+
1408+
@unittest.skipUnless(
1409+
hasattr(signal, "pidfd_send_signal"),
1410+
"pidfd support not built in",
1411+
)
1412+
def test_pidfd_send_signal(self):
1413+
with self.assertRaises(OSError) as cm:
1414+
signal.pidfd_send_signal(0, signal.SIGINT)
1415+
if cm.exception.errno == errno.ENOSYS:
1416+
self.skipTest("kernel does not support pidfds")
1417+
elif cm.exception.errno == errno.EPERM:
1418+
self.skipTest("Not enough privileges to use pidfs")
1419+
self.assertEqual(cm.exception.errno, errno.EBADF)
1420+
my_pidfd = os.open(f'/proc/{os.getpid()}', os.O_DIRECTORY)
1421+
self.addCleanup(os.close, my_pidfd)
1422+
with self.assertRaisesRegex(TypeError, "^siginfo must be None$"):
1423+
signal.pidfd_send_signal(my_pidfd, signal.SIGINT, object(), 0)
1424+
with self.assertRaises(KeyboardInterrupt):
1425+
signal.pidfd_send_signal(my_pidfd, signal.SIGINT)
1426+
13901427
def tearDownModule():
13911428
support.reap_children()
13921429

Lib/test/test_venv.py

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
Licensed to the PSF under a contributor agreement.
66
"""
77

8-
# pip isn't working yet
9-
# import ensurepip
8+
import ensurepip
109
import os
1110
import os.path
1211
import re
@@ -15,12 +14,12 @@
1514
import subprocess
1615
import sys
1716
import tempfile
18-
from test.support import captured_stdout, captured_stderr, requires_zlib
17+
from test.support import (captured_stdout, captured_stderr, requires_zlib,
18+
skip_if_broken_multiprocessing_synchronize)
1919
from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree)
20-
from test.support.import_helper import import_module
21-
import threading
2220
import unittest
2321
import venv
22+
from unittest.mock import patch
2423

2524
try:
2625
import ctypes
@@ -80,8 +79,8 @@ def run_with_capture(self, func, *args, **kwargs):
8079
def get_env_file(self, *args):
8180
return os.path.join(self.env_dir, *args)
8281

83-
def get_text_file_contents(self, *args):
84-
with open(self.get_env_file(*args), 'r') as f:
82+
def get_text_file_contents(self, *args, encoding='utf-8'):
83+
with open(self.get_env_file(*args), 'r', encoding=encoding) as f:
8584
result = f.read()
8685
return result
8786

@@ -139,6 +138,45 @@ def test_prompt(self):
139138
self.assertEqual(context.prompt, '(My prompt) ')
140139
self.assertIn("prompt = 'My prompt'\n", data)
141140

141+
rmtree(self.env_dir)
142+
builder = venv.EnvBuilder(prompt='.')
143+
cwd = os.path.basename(os.getcwd())
144+
self.run_with_capture(builder.create, self.env_dir)
145+
context = builder.ensure_directories(self.env_dir)
146+
data = self.get_text_file_contents('pyvenv.cfg')
147+
self.assertEqual(context.prompt, '(%s) ' % cwd)
148+
self.assertIn("prompt = '%s'\n" % cwd, data)
149+
150+
def test_upgrade_dependencies(self):
151+
builder = venv.EnvBuilder()
152+
bin_path = 'Scripts' if sys.platform == 'win32' else 'bin'
153+
python_exe = os.path.split(sys.executable)[1]
154+
with tempfile.TemporaryDirectory() as fake_env_dir:
155+
expect_exe = os.path.normcase(
156+
os.path.join(fake_env_dir, bin_path, python_exe)
157+
)
158+
if sys.platform == 'win32':
159+
expect_exe = os.path.normcase(os.path.realpath(expect_exe))
160+
161+
def pip_cmd_checker(cmd):
162+
cmd[0] = os.path.normcase(cmd[0])
163+
self.assertEqual(
164+
cmd,
165+
[
166+
expect_exe,
167+
'-m',
168+
'pip',
169+
'install',
170+
'--upgrade',
171+
'pip',
172+
'setuptools'
173+
]
174+
)
175+
176+
fake_context = builder.ensure_directories(fake_env_dir)
177+
with patch('venv.subprocess.check_call', pip_cmd_checker):
178+
builder.upgrade_dependencies(fake_context)
179+
142180
@requireVenvCreate
143181
def test_prefixes(self):
144182
"""
@@ -325,10 +363,11 @@ def test_multiprocessing(self):
325363
"""
326364
Test that the multiprocessing is able to spawn.
327365
"""
328-
# Issue bpo-36342: Instanciation of a Pool object imports the
366+
# bpo-36342: Instantiation of a Pool object imports the
329367
# multiprocessing.synchronize module. Skip the test if this module
330368
# cannot be imported.
331-
import_module('multiprocessing.synchronize')
369+
skip_if_broken_multiprocessing_synchronize()
370+
332371
rmtree(self.env_dir)
333372
self.run_with_capture(venv.create, self.env_dir)
334373
envpy = os.path.join(os.path.realpath(self.env_dir),
@@ -413,7 +452,7 @@ def do_test_with_pip(self, system_site_packages):
413452
# pip's cross-version compatibility may trigger deprecation
414453
# warnings in current versions of Python. Ensure related
415454
# environment settings don't cause venv to fail.
416-
envvars["PYTHONWARNINGS"] = "e"
455+
envvars["PYTHONWARNINGS"] = "ignore"
417456
# ensurepip is different enough from a normal pip invocation
418457
# that we want to ensure it ignores the normal pip environment
419458
# variable settings. We set PIP_NO_INSTALL here specifically
@@ -452,7 +491,8 @@ def do_test_with_pip(self, system_site_packages):
452491
# Ensure pip is available in the virtual environment
453492
envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe)
454493
# Ignore DeprecationWarning since pip code is not part of Python
455-
out, err = check_output([envpy, '-W', 'ignore::DeprecationWarning', '-I',
494+
out, err = check_output([envpy, '-W', 'ignore::DeprecationWarning',
495+
'-W', 'ignore::ImportWarning', '-I',
456496
'-m', 'pip', '--version'])
457497
# We force everything to text, so unittest gives the detailed diff
458498
# if we get unexpected results
@@ -468,8 +508,12 @@ def do_test_with_pip(self, system_site_packages):
468508
# Check the private uninstall command provided for the Windows
469509
# installers works (at least in a virtual environment)
470510
with EnvironmentVarGuard() as envvars:
511+
# It seems ensurepip._uninstall calls subprocesses which do not
512+
# inherit the interpreter settings.
513+
envvars["PYTHONWARNINGS"] = "ignore"
471514
out, err = check_output([envpy,
472-
'-W', 'ignore::DeprecationWarning', '-I',
515+
'-W', 'ignore::DeprecationWarning',
516+
'-W', 'ignore::ImportWarning', '-I',
473517
'-m', 'ensurepip._uninstall'])
474518
# We force everything to text, so unittest gives the detailed diff
475519
# if we get unexpected results
@@ -481,7 +525,7 @@ def do_test_with_pip(self, system_site_packages):
481525
# executing pip with sudo, you may want sudo's -H flag."
482526
# where $HOME is replaced by the HOME environment variable.
483527
err = re.sub("^(WARNING: )?The directory .* or its parent directory "
484-
"is not owned by the current user .*$", "",
528+
"is not owned or is not writable by the current user.*$", "",
485529
err, flags=re.MULTILINE)
486530
self.assertEqual(err.rstrip(), "")
487531
# Being fairly specific regarding the expected behaviour for the
@@ -497,10 +541,8 @@ def do_test_with_pip(self, system_site_packages):
497541
self.assert_pip_not_installed()
498542

499543
# Issue #26610: pip/pep425tags.py requires ctypes
500-
# TODO: RUSTPYTHON
501544
@unittest.skipUnless(ctypes, 'pip requires ctypes')
502-
@requires_zlib
503-
@unittest.expectedFailure
545+
@requires_zlib()
504546
def test_with_pip(self):
505547
self.do_test_with_pip(False)
506548
self.do_test_with_pip(True)

Lib/venv/__init__.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,20 @@ def create_if_needed(d):
142142
context.bin_name = binname
143143
context.env_exe = os.path.join(binpath, exename)
144144
create_if_needed(binpath)
145+
# Assign and update the command to use when launching the newly created
146+
# environment, in case it isn't simply the executable script (e.g. bpo-45337)
147+
context.env_exec_cmd = context.env_exe
148+
if sys.platform == 'win32':
149+
# bpo-45337: Fix up env_exec_cmd to account for file system redirections.
150+
# Some redirects only apply to CreateFile and not CreateProcess
151+
real_env_exe = os.path.realpath(context.env_exe)
152+
if os.path.normcase(real_env_exe) != os.path.normcase(context.env_exe):
153+
logger.warning('Actual environment location may have moved due to '
154+
'redirects, links or junctions.\n'
155+
' Requested location: "%s"\n'
156+
' Actual location: "%s"',
157+
context.env_exe, real_env_exe)
158+
context.env_exec_cmd = real_env_exe
145159
return context
146160

147161
def create_configuration(self, context):
@@ -267,8 +281,9 @@ def setup_python(self, context):
267281
os.path.normcase(f).startswith(('python', 'vcruntime'))
268282
]
269283
else:
270-
suffixes = ['python.exe', 'python_d.exe', 'pythonw.exe',
271-
'pythonw_d.exe']
284+
suffixes = {'python.exe', 'python_d.exe', 'pythonw.exe', 'pythonw_d.exe'}
285+
base_exe = os.path.basename(context.env_exe)
286+
suffixes.add(base_exe)
272287

273288
for suffix in suffixes:
274289
src = os.path.join(dirname, suffix)
@@ -290,15 +305,11 @@ def setup_python(self, context):
290305

291306
def _setup_pip(self, context):
292307
"""Installs or upgrades pip in a virtual environment"""
293-
# TODO: RustPython
294-
msg = ("Pip isn't supported yet. To create a virtual environment"
295-
"without pip, call venv with the --without-pip flag.")
296-
raise NotImplementedError(msg)
297308
# We run ensurepip in isolated mode to avoid side effects from
298309
# environment vars, the current directory and anything else
299310
# intended for the global Python environment
300-
cmd = [context.env_exe, '-Im', 'ensurepip', '--upgrade',
301-
'--default-pip']
311+
cmd = [context.env_exec_cmd, '-Im', 'ensurepip', '--upgrade',
312+
'--default-pip']
302313
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
303314

304315
def setup_scripts(self, context):
@@ -398,11 +409,7 @@ def upgrade_dependencies(self, context):
398409
logger.debug(
399410
f'Upgrading {CORE_VENV_DEPS} packages in {context.bin_path}'
400411
)
401-
if sys.platform == 'win32':
402-
python_exe = os.path.join(context.bin_path, 'python.exe')
403-
else:
404-
python_exe = os.path.join(context.bin_path, 'python')
405-
cmd = [python_exe, '-m', 'pip', 'install', '--upgrade']
412+
cmd = [context.env_exec_cmd, '-m', 'pip', 'install', '--upgrade']
406413
cmd.extend(CORE_VENV_DEPS)
407414
subprocess.check_call(cmd)
408415

0 commit comments

Comments
 (0)