Skip to content

Commit

Permalink
Updates tests to 3.11.8; get 3.11.8 tests passing locally.
Browse files Browse the repository at this point in the history
  • Loading branch information
jamadden committed Feb 12, 2024
1 parent 080d2bb commit a65839c
Show file tree
Hide file tree
Showing 48 changed files with 2,918 additions and 176 deletions.
9 changes: 9 additions & 0 deletions docs/changes/2020.bugfix
@@ -0,0 +1,9 @@
Add support for Python patch releases 3.11.8 and 3.12.2, which changed
internal details of threading.

Other updates for compatibility with the standard library include:

- Errors raised from ``subprocess.Popen`` may not have a filename set.
- ``SSLSocket.recv_into`` and ``SSLSocket.read`` no longer require the
buffer to implement ``len`` and now work with buffers whose size is
not 1.
4 changes: 2 additions & 2 deletions pyproject.toml
Expand Up @@ -18,7 +18,7 @@ requires = [
# 3.0a6 fixes an issue cythonizing source on 32-bit platforms.
# 3.0a9 is needed for Python 3.10.
# Python 3.12 requires at least 3.0rc2.
"Cython >= 3.0.2",
"Cython >= 3.0.8",
# See version requirements in setup.py
"cffi >= 1.12.3 ; platform_python_implementation == 'CPython'",
# Python 3.7 requires at least 0.4.14, which is ABI incompatible with earlier
Expand All @@ -30,7 +30,7 @@ requires = [
# 3.0 is ABI compatible with earlier releases, so we can switch back and
# forth between 2 and 3 without recompiling. 3.0 is required for
# Python 3.12, and it fixes some serious bugs in Python 3.11.
"greenlet >= 3.0rc3 ; platform_python_implementation == 'CPython'",
"greenlet >= 3.0.3 ; platform_python_implementation == 'CPython'",
]

[tool.towncrier]
Expand Down
2 changes: 1 addition & 1 deletion src/gevent/libev/corecext.pyx
Expand Up @@ -1354,7 +1354,7 @@ cdef public class stat(watcher) [object PyGeventStatObject, type PyGeventStat_Ty
cdef object SYSERR_CALLBACK = None


cdef void _syserr_cb(char* msg) with gil:
cdef void _syserr_cb(char* msg) noexcept with gil:
try:
SYSERR_CALLBACK(msg, errno)
except:
Expand Down
34 changes: 26 additions & 8 deletions src/gevent/ssl.py
Expand Up @@ -373,13 +373,22 @@ def _check_connected(self):

def read(self, nbytes=2014, buffer=None):
"""Read up to LEN bytes and return them.
Return zero-length string on EOF."""
Return zero-length string on EOF.
.. versionchanged:: NEXT
No longer requires a non-None *buffer* to implement ``len()``.
This is a backport from 3.11.8.
"""
# pylint:disable=too-many-branches
self._checkClosed()
# The stdlib signature is (len=1024, buffer=None)
# but that shadows the len builtin, and its hard/annoying to
# get it back.
initial_buf_len = len(buffer) if buffer is not None else None
#
# Also, the return values are weird. If *buffer* is given,
# we return the count of bytes added to buffer. Otherwise,
# we return the string we read.
bytes_read = 0
while True:
if not self._sslobj:
raise ValueError("Read on closed or unwrapped SSL socket.")
Expand All @@ -389,7 +398,8 @@ def read(self, nbytes=2014, buffer=None):
# to raise a ValueError
try:
if buffer is not None:
return self._sslobj.read(nbytes, buffer)
bytes_read += self._sslobj.read(nbytes, buffer)
return bytes_read
return self._sslobj.read(nbytes or 1024)
except SSLWantReadError:
if self.timeout == 0.0:
Expand All @@ -402,7 +412,7 @@ def read(self, nbytes=2014, buffer=None):
self._wait(self._write_event, timeout_exc=_SSLErrorReadTimeout)
except SSLError as ex:
if ex.args[0] == SSL_ERROR_EOF and self.suppress_ragged_eofs:
return b'' if buffer is None else len(buffer) - initial_buf_len
return b'' if buffer is None else bytes_read
raise
# Certain versions of Python, built against certain
# versions of OpenSSL operating in certain modes, can
Expand Down Expand Up @@ -563,11 +573,19 @@ def recv(self, buflen=1024, flags=0):
return socket.recv(self, buflen, flags)

def recv_into(self, buffer, nbytes=None, flags=0):
"""
.. versionchanged:: NEXT
No longer requires a non-None *buffer* to implement ``len()``.
This is a backport from 3.11.8.
"""
self._checkClosed()
if buffer and (nbytes is None):
nbytes = len(buffer)
elif nbytes is None:
nbytes = 1024
if nbytes is None:
if buffer is not None:
with memoryview(buffer) as view:
nbytes = view.nbytes
if not nbytes:
nbytes = 1024

if self._sslobj:
if flags != 0:
raise ValueError("non-zero flags not allowed in calls to recv_into() on %s" % self.__class__)
Expand Down
70 changes: 47 additions & 23 deletions src/gevent/subprocess.py
Expand Up @@ -401,6 +401,7 @@ def check_output(*popenargs, **kwargs):
kwargs['stdin'] = PIPE
else:
inputdata = None

with Popen(*popenargs, stdout=PIPE, **kwargs) as process:
try:
output, unused_err = process.communicate(inputdata, timeout=timeout)
Expand Down Expand Up @@ -1629,6 +1630,19 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
if self.pid == 0:
# Child

# In various places on the child side of things, we catch OSError
# and add attributes to it that detail where in the process we failed;
# like all exceptions until we have exec'd, this exception is pickled
# and sent to the parent to raise in the calling process.
# The parent uses this to decide how to treat that exception,
# adjusting certain information about it as needed.
#
# Python 3.11.8 --- yes, a minor patch release --- stopped
# letting the 'filename' parameter get set in the resulting
# exception for many cases. We're not quite interpreting this
# the same way the stdlib is, I'm sure, but this makes the stdlib
# tests pass.

# XXX: Technically we're doing a lot of stuff here that
# may not be safe to do before a exec(), depending on the OS.
# CPython 3 goes to great lengths to precompute a lot
Expand Down Expand Up @@ -1702,14 +1716,19 @@ def _dup2(existing, desired):
os.umask(umask)
# XXX: CPython does _Py_RestoreSignals here.
# Then setsid() based on ???
if gids:
os.setgroups(gids)
if gid:
os.setregid(gid, gid)
if uid:
os.setreuid(uid, uid)
if process_group is not None:
os.setpgid(0, process_group)
try:
if gids:
os.setgroups(gids)
if gid:
os.setregid(gid, gid)
if uid:
os.setreuid(uid, uid)
if process_group is not None:
os.setpgid(0, process_group)
except OSError as e:
e._failed_chuser = True
raise

if preexec_fn:
preexec_fn()

Expand All @@ -1731,21 +1750,24 @@ def _dup2(existing, desired):
if start_new_session:
os.setsid()

if env is None:
os.execvp(executable, args)
else:
# Python 3.6 started testing for
# bytes values in the env; it also
# started encoding strs using
# fsencode and using a lower-level
# API that takes a list of keys
# and values. We don't have access
# to that API, so we go the reverse direction.
env = {os.fsdecode(k) if isinstance(k, bytes) else k:
os.fsdecode(v) if isinstance(v, bytes) else v
for k, v in env.items()}
os.execvpe(executable, args, env)

try:
if env is None:
os.execvp(executable, args)
else:
# Python 3.6 started testing for
# bytes values in the env; it also
# started encoding strs using
# fsencode and using a lower-level
# API that takes a list of keys
# and values. We don't have access
# to that API, so we go the reverse direction.
env = {os.fsdecode(k) if isinstance(k, bytes) else k:
os.fsdecode(v) if isinstance(v, bytes) else v
for k, v in env.items()}
os.execvpe(executable, args, env)
except OSError as e:
e._failed_exec = True
raise
except:
exc_type, exc_value, tb = sys.exc_info()
# Save the traceback and attach it to the exception object
Expand Down Expand Up @@ -1811,6 +1833,8 @@ def _dup2(existing, desired):
child_exception.filename = executable
if hasattr(child_exception, '_failed_chdir'):
child_exception.filename = cwd
if getattr(child_exception, '_failed_chuser', False):
child_exception.filename = None
raise child_exception

def _handle_exitstatus(self, sts, _WIFSIGNALED=os.WIFSIGNALED,
Expand Down
12 changes: 12 additions & 0 deletions src/gevent/testing/monkey_test.py
Expand Up @@ -22,6 +22,7 @@
from . import util



# This uses the internal built-in function ``_thread._count()``,
# which we don't/can't monkey-patch, so it returns inaccurate information.
def threading_setup():
Expand Down Expand Up @@ -87,6 +88,17 @@ def wait_threads_exit(timeout=None): # pylint:disable=unused-argument
threading_helper.threading_setup = threading_setup
threading_helper.threading_cleanup = threading_cleanup

# So we don't have to patch test_threading to use our
# version of lock_tests, we patch
from gevent.tests import lock_tests
try:
import test.lock_tests
except ImportError:
pass
else:
test.lock_tests = lock_tests
sys.modules['tests.lock_tests'] = lock_tests

# Configure allowed resources
resources.setup_resources()

Expand Down

0 comments on commit a65839c

Please sign in to comment.