diff options
Diffstat (limited to 'Lib')
246 files changed, 7897 insertions, 3919 deletions
diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 36cd993000..87302ac76d 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -10,6 +10,10 @@ from abc import ABCMeta, abstractmethod import sys GenericAlias = type(list[int]) +EllipsisType = type(...) +def _f(): pass +FunctionType = type(_f) +del _f __all__ = ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator", "AsyncGenerator", @@ -409,6 +413,95 @@ class Collection(Sized, Iterable, Container): return NotImplemented +class _CallableGenericAlias(GenericAlias): + """ Represent `Callable[argtypes, resulttype]`. + + This sets ``__args__`` to a tuple containing the flattened ``argtypes`` + followed by ``resulttype``. + + Example: ``Callable[[int, str], float]`` sets ``__args__`` to + ``(int, str, float)``. + """ + + __slots__ = () + + def __new__(cls, origin, args): + return cls.__create_ga(origin, args) + + @classmethod + def __create_ga(cls, origin, args): + if not isinstance(args, tuple) or len(args) != 2: + raise TypeError( + "Callable must be used as Callable[[arg, ...], result].") + t_args, t_result = args + if isinstance(t_args, (list, tuple)): + ga_args = tuple(t_args) + (t_result,) + # This relaxes what t_args can be on purpose to allow things like + # PEP 612 ParamSpec. Responsibility for whether a user is using + # Callable[...] properly is deferred to static type checkers. + else: + ga_args = args + return super().__new__(cls, origin, ga_args) + + def __repr__(self): + if _has_special_args(self.__args__): + return super().__repr__() + return (f'collections.abc.Callable' + f'[[{", ".join([_type_repr(a) for a in self.__args__[:-1]])}], ' + f'{_type_repr(self.__args__[-1])}]') + + def __reduce__(self): + args = self.__args__ + if not _has_special_args(args): + args = list(args[:-1]), args[-1] + return _CallableGenericAlias, (Callable, args) + + def __getitem__(self, item): + # Called during TypeVar substitution, returns the custom subclass + # rather than the default types.GenericAlias object. + ga = super().__getitem__(item) + args = ga.__args__ + # args[0] occurs due to things like Z[[int, str, bool]] from PEP 612 + if not isinstance(ga.__args__[0], tuple): + t_result = ga.__args__[-1] + t_args = ga.__args__[:-1] + args = (t_args, t_result) + return _CallableGenericAlias(Callable, args) + + +def _has_special_args(args): + """Checks if args[0] matches either ``...``, ``ParamSpec`` or + ``_ConcatenateGenericAlias`` from typing.py + """ + if len(args) != 2: + return False + obj = args[0] + if obj is Ellipsis: + return True + obj = type(obj) + names = ('ParamSpec', '_ConcatenateGenericAlias') + return obj.__module__ == 'typing' and any(obj.__name__ == name for name in names) + + +def _type_repr(obj): + """Return the repr() of an object, special-casing types (internal helper). + + Copied from :mod:`typing` since collections.abc + shouldn't depend on that module. + """ + if isinstance(obj, GenericAlias): + return repr(obj) + if isinstance(obj, type): + if obj.__module__ == 'builtins': + return obj.__qualname__ + return f'{obj.__module__}.{obj.__qualname__}' + if obj is Ellipsis: + return '...' + if isinstance(obj, FunctionType): + return obj.__name__ + return repr(obj) + + class Callable(metaclass=ABCMeta): __slots__ = () @@ -423,14 +516,13 @@ class Callable(metaclass=ABCMeta): return _check_methods(C, "__call__") return NotImplemented - __class_getitem__ = classmethod(GenericAlias) + __class_getitem__ = classmethod(_CallableGenericAlias) ### SETS ### class Set(Collection): - """A set is a finite, iterable container. This class provides concrete generic implementations of all @@ -657,17 +749,15 @@ MutableSet.register(set) class Mapping(Collection): - - __slots__ = () - """A Mapping is a generic container for associating key/value pairs. This class provides concrete generic implementations of all methods except for __getitem__, __iter__, and __len__. - """ + __slots__ = () + @abstractmethod def __getitem__(self, key): raise KeyError @@ -789,18 +879,16 @@ ValuesView.register(dict_values) class MutableMapping(Mapping): - - __slots__ = () - """A MutableMapping is a generic container for associating key/value pairs. This class provides concrete generic implementations of all methods except for __getitem__, __setitem__, __delitem__, __iter__, and __len__. - """ + __slots__ = () + @abstractmethod def __setitem__(self, key, value): raise KeyError @@ -879,7 +967,6 @@ MutableMapping.register(dict) class Sequence(Reversible, Collection): - """All the operations on a read-only sequence. Concrete subclasses must override __new__ or __init__, @@ -947,7 +1034,6 @@ Sequence.register(memoryview) class ByteString(Sequence): - """This unifies bytes and bytearray. XXX Should add all their methods. @@ -960,16 +1046,14 @@ ByteString.register(bytearray) class MutableSequence(Sequence): - - __slots__ = () - """All the operations on a read-write sequence. Concrete subclasses must provide __new__ or __init__, __getitem__, __setitem__, __delitem__, __len__, and insert(). - """ + __slots__ = () + @abstractmethod def __setitem__(self, index, value): raise IndexError diff --git a/Lib/_osx_support.py b/Lib/_osx_support.py index e9efce7d7e..37975fe8a3 100644 --- a/Lib/_osx_support.py +++ b/Lib/_osx_support.py @@ -52,7 +52,7 @@ def _find_executable(executable, path=None): return executable -def _read_output(commandstring): +def _read_output(commandstring, capture_stderr=False): """Output from successful command execution or None""" # Similar to os.popen(commandstring, "r").read(), # but without actually using os.popen because that @@ -67,7 +67,10 @@ def _read_output(commandstring): os.getpid(),), "w+b") with contextlib.closing(fp) as fp: - cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name) + if capture_stderr: + cmd = "%s >'%s' 2>&1" % (commandstring, fp.name) + else: + cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name) return fp.read().decode('utf-8').strip() if not os.system(cmd) else None @@ -110,6 +113,26 @@ def _get_system_version(): return _SYSTEM_VERSION +_SYSTEM_VERSION_TUPLE = None +def _get_system_version_tuple(): + """ + Return the macOS system version as a tuple + + The return value is safe to use to compare + two version numbers. + """ + global _SYSTEM_VERSION_TUPLE + if _SYSTEM_VERSION_TUPLE is None: + osx_version = _get_system_version() + if osx_version: + try: + _SYSTEM_VERSION_TUPLE = tuple(int(i) for i in osx_version.split('.')) + except ValueError: + _SYSTEM_VERSION_TUPLE = () + + return _SYSTEM_VERSION_TUPLE + + def _remove_original_values(_config_vars): """Remove original unmodified values for testing""" # This is needed for higher-level cross-platform tests of get_platform. @@ -125,6 +148,33 @@ def _save_modified_value(_config_vars, cv, newvalue): _config_vars[_INITPRE + cv] = oldvalue _config_vars[cv] = newvalue + +_cache_default_sysroot = None +def _default_sysroot(cc): + """ Returns the root of the default SDK for this system, or '/' """ + global _cache_default_sysroot + + if _cache_default_sysroot is not None: + return _cache_default_sysroot + + contents = _read_output('%s -c -E -v - </dev/null' % (cc,), True) + in_incdirs = False + for line in contents.splitlines(): + if line.startswith("#include <...>"): + in_incdirs = True + elif line.startswith("End of search list"): + in_incdirs = False + elif in_incdirs: + line = line.strip() + if line == '/usr/include': + _cache_default_sysroot = '/' + elif line.endswith(".sdk/usr/include"): + _cache_default_sysroot = line[:-12] + if _cache_default_sysroot is None: + _cache_default_sysroot = '/' + + return _cache_default_sysroot + def _supports_universal_builds(): """Returns True if universal builds are supported on this system""" # As an approximation, we assume that if we are running on 10.4 or above, @@ -132,14 +182,18 @@ def _supports_universal_builds(): # builds, in particular -isysroot and -arch arguments to the compiler. This # is in support of allowing 10.4 universal builds to run on 10.3.x systems. - osx_version = _get_system_version() - if osx_version: - try: - osx_version = tuple(int(i) for i in osx_version.split('.')) - except ValueError: - osx_version = '' + osx_version = _get_system_version_tuple() return bool(osx_version >= (10, 4)) if osx_version else False +def _supports_arm64_builds(): + """Returns True if arm64 builds are supported on this system""" + # There are two sets of systems supporting macOS/arm64 builds: + # 1. macOS 11 and later, unconditionally + # 2. macOS 10.15 with Xcode 12.2 or later + # For now the second category is ignored. + osx_version = _get_system_version_tuple() + return osx_version >= (11, 0) if osx_version else False + def _find_appropriate_compiler(_config_vars): """Find appropriate C compiler for extension module builds""" @@ -331,6 +385,12 @@ def compiler_fixup(compiler_so, cc_args): except ValueError: break + elif not _supports_arm64_builds(): + # Look for "-arch arm64" and drop that + for idx in reversed(range(len(compiler_so))): + if compiler_so[idx] == '-arch' and compiler_so[idx+1] == "arm64": + del compiler_so[idx:idx+2] + if 'ARCHFLAGS' in os.environ and not stripArch: # User specified different -arch flags in the environ, # see also distutils.sysconfig @@ -481,6 +541,8 @@ def get_platform_osx(_config_vars, osname, release, machine): if len(archs) == 1: machine = archs[0] + elif archs == ('arm64', 'x86_64'): + machine = 'universal2' elif archs == ('i386', 'ppc'): machine = 'fat' elif archs == ('i386', 'x86_64'): diff --git a/Lib/argparse.py b/Lib/argparse.py index 2fb1da59f9..8a12dea766 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1719,7 +1719,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): add_group = self.add_argument_group self._positionals = add_group(_('positional arguments')) - self._optionals = add_group(_('optional arguments')) + self._optionals = add_group(_('options')) self._subparsers = None # register types diff --git a/Lib/ast.py b/Lib/ast.py index d8bd337370..845c80c2bb 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -63,7 +63,10 @@ def literal_eval(node_or_string): if isinstance(node_or_string, Expression): node_or_string = node_or_string.body def _raise_malformed_node(node): - raise ValueError(f'malformed node or string: {node!r}') + msg = "malformed node or string" + if lno := getattr(node, 'lineno', None): + msg += f' on line {lno}' + raise ValueError(msg + f': {node!r}') def _convert_num(node): if not isinstance(node, Constant) or type(node.value) not in (int, float, complex): _raise_malformed_node(node) @@ -662,17 +665,23 @@ class _Precedence(IntEnum): except ValueError: return self + +_SINGLE_QUOTES = ("'", '"') +_MULTI_QUOTES = ('"""', "'''") +_ALL_QUOTES = (*_SINGLE_QUOTES, *_MULTI_QUOTES) + class _Unparser(NodeVisitor): """Methods in this class recursively traverse an AST and output source code for the abstract syntax; original formatting is disregarded.""" - def __init__(self): + def __init__(self, *, _avoid_backslashes=False): self._source = [] self._buffer = [] self._precedences = {} self._type_ignores = {} self._indent = 0 + self._avoid_backslashes = _avoid_backslashes def interleave(self, inter, f, seq): """Call f on each item in seq, calling inter() in between.""" @@ -1067,15 +1076,85 @@ class _Unparser(NodeVisitor): with self.block(extra=self.get_type_comment(node)): self.traverse(node.body) + def _str_literal_helper( + self, string, *, quote_types=_ALL_QUOTES, escape_special_whitespace=False + ): + """Helper for writing string literals, minimizing escapes. + Returns the tuple (string literal to write, possible quote types). + """ + def escape_char(c): + # \n and \t are non-printable, but we only escape them if + # escape_special_whitespace is True + if not escape_special_whitespace and c in "\n\t": + return c + # Always escape backslashes and other non-printable characters + if c == "\\" or not c.isprintable(): + return c.encode("unicode_escape").decode("ascii") + return c + + escaped_string = "".join(map(escape_char, string)) + possible_quotes = quote_types + if "\n" in escaped_string: + possible_quotes = [q for q in possible_quotes if q in _MULTI_QUOTES] + possible_quotes = [q for q in possible_quotes if q not in escaped_string] + if not possible_quotes: + # If there aren't any possible_quotes, fallback to using repr + # on the original string. Try to use a quote from quote_types, + # e.g., so that we use triple quotes for docstrings. + string = repr(string) + quote = next((q for q in quote_types if string[0] in q), string[0]) + return string[1:-1], [quote] + if escaped_string: + # Sort so that we prefer '''"''' over """\"""" + possible_quotes.sort(key=lambda q: q[0] == escaped_string[-1]) + # If we're using triple quotes and we'd need to escape a final + # quote, escape it + if possible_quotes[0][0] == escaped_string[-1]: + assert len(possible_quotes[0]) == 3 + escaped_string = escaped_string[:-1] + "\\" + escaped_string[-1] + return escaped_string, possible_quotes + + def _write_str_avoiding_backslashes(self, string, *, quote_types=_ALL_QUOTES): + """Write string literal value with a best effort attempt to avoid backslashes.""" + string, quote_types = self._str_literal_helper(string, quote_types=quote_types) + quote_type = quote_types[0] + self.write(f"{quote_type}{string}{quote_type}") + def visit_JoinedStr(self, node): self.write("f") - self._fstring_JoinedStr(node, self.buffer_writer) - self.write(repr(self.buffer)) + if self._avoid_backslashes: + self._fstring_JoinedStr(node, self.buffer_writer) + self._write_str_avoiding_backslashes(self.buffer) + return + + # If we don't need to avoid backslashes globally (i.e., we only need + # to avoid them inside FormattedValues), it's cosmetically preferred + # to use escaped whitespace. That is, it's preferred to use backslashes + # for cases like: f"{x}\n". To accomplish this, we keep track of what + # in our buffer corresponds to FormattedValues and what corresponds to + # Constant parts of the f-string, and allow escapes accordingly. + buffer = [] + for value in node.values: + meth = getattr(self, "_fstring_" + type(value).__name__) + meth(value, self.buffer_writer) + buffer.append((self.buffer, isinstance(value, Constant))) + new_buffer = [] + quote_types = _ALL_QUOTES + for value, is_constant in buffer: + # Repeatedly narrow down the list of possible quote_types + value, quote_types = self._str_literal_helper( + value, quote_types=quote_types, + escape_special_whitespace=is_constant + ) + new_buffer.append(value) + value = "".join(new_buffer) + quote_type = quote_types[0] + self.write(f"{quote_type}{value}{quote_type}") def visit_FormattedValue(self, node): self.write("f") self._fstring_FormattedValue(node, self.buffer_writer) - self.write(repr(self.buffer)) + self._write_str_avoiding_backslashes(self.buffer) def _fstring_JoinedStr(self, node, write): for value in node.values: @@ -1090,11 +1169,13 @@ class _Unparser(NodeVisitor): def _fstring_FormattedValue(self, node, write): write("{") - unparser = type(self)() + unparser = type(self)(_avoid_backslashes=True) unparser.set_precedence(_Precedence.TEST.next(), node.value) expr = unparser.visit(node.value) if expr.startswith("{"): write(" ") # Separate pair of opening brackets as "{ {" + if "\\" in expr: + raise ValueError("Unable to avoid backslash in f-string expression part") write(expr) if node.conversion != -1: conversion = chr(node.conversion) @@ -1111,33 +1192,17 @@ class _Unparser(NodeVisitor): self.write(node.id) def _write_docstring(self, node): - def esc_char(c): - if c in ("\n", "\t"): - # In the AST form, we don't know the author's intentation - # about how this should be displayed. We'll only escape - # \n and \t, because they are more likely to be unescaped - # in the source - return c - return c.encode('unicode_escape').decode('ascii') - self.fill() if node.kind == "u": self.write("u") - - value = node.value - if value: - # Preserve quotes in the docstring by escaping them - value = "".join(map(esc_char, value)) - if value[-1] == '"': - value = value.replace('"', '\\"', -1) - value = value.replace('"""', '""\\"') - - self.write(f'"""{value}"""') + self._write_str_avoiding_backslashes(node.value, quote_types=_MULTI_QUOTES) def _write_constant(self, value): if isinstance(value, (float, complex)): # Substitute overflowing decimal literal for AST infinities. self.write(repr(value).replace("inf", _INFSTR)) + elif self._avoid_backslashes and isinstance(value, str): + self._write_str_avoiding_backslashes(value) else: self.write(repr(value)) diff --git a/Lib/asyncio/__init__.py b/Lib/asyncio/__init__.py index eb84bfb189..200b14c2a3 100644 --- a/Lib/asyncio/__init__.py +++ b/Lib/asyncio/__init__.py @@ -20,10 +20,6 @@ from .tasks import * from .threads import * from .transports import * -# Exposed for _asynciomodule.c to implement now deprecated -# Task.all_tasks() method. This function will be removed in 3.9. -from .tasks import _all_tasks_compat # NoQA - __all__ = (base_events.__all__ + coroutines.__all__ + events.__all__ + diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index b2d446a51f..f789635e0f 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -350,7 +350,7 @@ class Server(events.AbstractServer): self._start_serving() # Skip one loop iteration so that all 'loop.add_reader' # go through. - await tasks.sleep(0, loop=self._loop) + await tasks.sleep(0) async def serve_forever(self): if self._serving_forever_fut is not None: @@ -541,8 +541,7 @@ class BaseEventLoop(events.AbstractEventLoop): results = await tasks.gather( *[ag.aclose() for ag in closing_agens], - return_exceptions=True, - loop=self) + return_exceptions=True) for result, agen in zip(results, closing_agens): if isinstance(result, Exception): @@ -1457,7 +1456,7 @@ class BaseEventLoop(events.AbstractEventLoop): fs = [self._create_server_getaddrinfo(host, port, family=family, flags=flags) for host in hosts] - infos = await tasks.gather(*fs, loop=self) + infos = await tasks.gather(*fs) infos = set(itertools.chain.from_iterable(infos)) completed = False @@ -1515,7 +1514,7 @@ class BaseEventLoop(events.AbstractEventLoop): server._start_serving() # Skip one loop iteration so that all 'loop.add_reader' # go through. - await tasks.sleep(0, loop=self) + await tasks.sleep(0) if self._debug: logger.info("%r is serving", server) @@ -1525,14 +1524,6 @@ class BaseEventLoop(events.AbstractEventLoop): self, protocol_factory, sock, *, ssl=None, ssl_handshake_timeout=None): - """Handle an accepted connection. - - This is used by servers that accept connections outside of - asyncio but that use asyncio to handle connections. - - This method is a coroutine. When completed, the coroutine - returns a (transport, protocol) pair. - """ if sock.type != socket.SOCK_STREAM: raise ValueError(f'A Stream Socket was expected, got {sock!r}') diff --git a/Lib/asyncio/base_futures.py b/Lib/asyncio/base_futures.py index 22f298069c..2c01ac98e1 100644 --- a/Lib/asyncio/base_futures.py +++ b/Lib/asyncio/base_futures.py @@ -1,6 +1,7 @@ __all__ = () import reprlib +from _thread import get_ident from . import format_helpers @@ -41,6 +42,16 @@ def _format_callbacks(cb): return f'cb=[{cb}]' +# bpo-42183: _repr_running is needed for repr protection +# when a Future or Task result contains itself directly or indirectly. +# The logic is borrowed from @reprlib.recursive_repr decorator. +# Unfortunately, the direct decorator usage is impossible because of +# AttributeError: '_asyncio.Task' object has no attribute '__module__' error. +# +# After fixing this thing we can return to the decorator based approach. +_repr_running = set() + + def _future_repr_info(future): # (Future) -> str """helper function for Future.__repr__""" @@ -49,9 +60,17 @@ def _future_repr_info(future): if future._exception is not None: info.append(f'exception={future._exception!r}') else: - # use reprlib to limit the length of the output, especially - # for very long strings - result = reprlib.repr(future._result) + key = id(future), get_ident() + if key in _repr_running: + result = '...' + else: + _repr_running.add(key) + try: + # use reprlib to limit the length of the output, especially + # for very long strings + result = reprlib.repr(future._result) + finally: + _repr_running.discard(key) info.append(f'result={result}') if future._callbacks: info.append(_format_callbacks(future._callbacks)) diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 0dce87b8ec..1a20f362ec 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -418,6 +418,20 @@ class AbstractEventLoop: """ raise NotImplementedError + async def connect_accepted_socket( + self, protocol_factory, sock, + *, ssl=None, + ssl_handshake_timeout=None): + """Handle an accepted connection. + + This is used by servers that accept connections outside of + asyncio, but use asyncio to handle connections. + + This method is a coroutine. When completed, the coroutine + returns a (transport, protocol) pair. + """ + raise NotImplementedError + async def create_datagram_endpoint(self, protocol_factory, local_addr=None, remote_addr=None, *, family=0, proto=0, flags=0, diff --git a/Lib/asyncio/exceptions.py b/Lib/asyncio/exceptions.py index e03602ef57..f07e448657 100644 --- a/Lib/asyncio/exceptions.py +++ b/Lib/asyncio/exceptions.py @@ -34,8 +34,9 @@ class IncompleteReadError(EOFError): - expected: total number of expected bytes (or None if unknown) """ def __init__(self, partial, expected): + r_expected = 'undefined' if expected is None else repr(expected) super().__init__(f'{len(partial)} bytes read on a total of ' - f'{expected!r} expected bytes') + f'{r_expected} expected bytes') self.partial = partial self.expected = expected diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py index bed4da52fd..2d22ef66c9 100644 --- a/Lib/asyncio/futures.py +++ b/Lib/asyncio/futures.py @@ -115,7 +115,7 @@ class Future: @_log_traceback.setter def _log_traceback(self, val): - if bool(val): + if val: raise ValueError('_log_traceback can only be set to False') self.__log_traceback = False diff --git a/Lib/asyncio/locks.py b/Lib/asyncio/locks.py index f1ce732478..a7453fb1c7 100644 --- a/Lib/asyncio/locks.py +++ b/Lib/asyncio/locks.py @@ -3,10 +3,9 @@ __all__ = ('Lock', 'Event', 'Condition', 'Semaphore', 'BoundedSemaphore') import collections -import warnings -from . import events from . import exceptions +from . import mixins class _ContextManagerMixin: @@ -20,7 +19,7 @@ class _ContextManagerMixin: self.release() -class Lock(_ContextManagerMixin): +class Lock(_ContextManagerMixin, mixins._LoopBoundMixin): """Primitive lock objects. A primitive lock is a synchronization primitive that is not owned @@ -74,16 +73,10 @@ class Lock(_ContextManagerMixin): """ - def __init__(self, *, loop=None): + def __init__(self, *, loop=mixins._marker): + super().__init__(loop=loop) self._waiters = None self._locked = False - if loop is None: - self._loop = events.get_event_loop() - else: - self._loop = loop - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) def __repr__(self): res = super().__repr__() @@ -109,7 +102,7 @@ class Lock(_ContextManagerMixin): if self._waiters is None: self._waiters = collections.deque() - fut = self._loop.create_future() + fut = self._get_loop().create_future() self._waiters.append(fut) # Finally block should be called before the CancelledError @@ -161,7 +154,7 @@ class Lock(_ContextManagerMixin): fut.set_result(True) -class Event: +class Event(mixins._LoopBoundMixin): """Asynchronous equivalent to threading.Event. Class implementing event objects. An event manages a flag that can be set @@ -170,16 +163,10 @@ class Event: false. """ - def __init__(self, *, loop=None): + def __init__(self, *, loop=mixins._marker): + super().__init__(loop=loop) self._waiters = collections.deque() self._value = False - if loop is None: - self._loop = events.get_event_loop() - else: - self._loop = loop - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) def __repr__(self): res = super().__repr__() @@ -220,7 +207,7 @@ class Event: if self._value: return True - fut = self._loop.create_future() + fut = self._get_loop().create_future() self._waiters.append(fut) try: await fut @@ -229,7 +216,7 @@ class Event: self._waiters.remove(fut) -class Condition(_ContextManagerMixin): +class Condition(_ContextManagerMixin, mixins._LoopBoundMixin): """Asynchronous equivalent to threading.Condition. This class implements condition variable objects. A condition variable @@ -239,18 +226,11 @@ class Condition(_ContextManagerMixin): A new Lock object is created and used as the underlying lock. """ - def __init__(self, lock=None, *, loop=None): - if loop is None: - self._loop = events.get_event_loop() - else: - self._loop = loop - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) - + def __init__(self, lock=None, *, loop=mixins._marker): + super().__init__(loop=loop) if lock is None: - lock = Lock(loop=loop) - elif lock._loop is not self._loop: + lock = Lock() + elif lock._loop is not self._get_loop(): raise ValueError("loop argument must agree with lock") self._lock = lock @@ -284,7 +264,7 @@ class Condition(_ContextManagerMixin): self.release() try: - fut = self._loop.create_future() + fut = self._get_loop().create_future() self._waiters.append(fut) try: await fut @@ -351,7 +331,7 @@ class Condition(_ContextManagerMixin): self.notify(len(self._waiters)) -class Semaphore(_ContextManagerMixin): +class Semaphore(_ContextManagerMixin, mixins._LoopBoundMixin): """A Semaphore implementation. A semaphore manages an internal counter which is decremented by each @@ -366,18 +346,12 @@ class Semaphore(_ContextManagerMixin): ValueError is raised. """ - def __init__(self, value=1, *, loop=None): + def __init__(self, value=1, *, loop=mixins._marker): + super().__init__(loop=loop) if value < 0: raise ValueError("Semaphore initial value must be >= 0") self._value = value self._waiters = collections.deque() - if loop is None: - self._loop = events.get_event_loop() - else: - self._loop = loop - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) def __repr__(self): res = super().__repr__() @@ -407,7 +381,7 @@ class Semaphore(_ContextManagerMixin): True. """ while self._value <= 0: - fut = self._loop.create_future() + fut = self._get_loop().create_future() self._waiters.append(fut) try: await fut @@ -436,12 +410,7 @@ class BoundedSemaphore(Semaphore): above the initial value. """ - def __init__(self, value=1, *, loop=None): - if loop: - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) - + def __init__(self, value=1, *, loop=mixins._marker): self._bound_value = value super().__init__(value, loop=loop) diff --git a/Lib/asyncio/mixins.py b/Lib/asyncio/mixins.py new file mode 100644 index 0000000000..650df05ccc --- /dev/null +++ b/Lib/asyncio/mixins.py @@ -0,0 +1,31 @@ +"""Event loop mixins.""" + +import threading +from . import events + +_global_lock = threading.Lock() + +# Used as a sentinel for loop parameter +_marker = object() + + +class _LoopBoundMixin: + _loop = None + + def __init__(self, *, loop=_marker): + if loop is not _marker: + raise TypeError( + f'As of 3.10, the *loop* parameter was removed from ' + f'{type(self).__name__}() since it is no longer necessary' + ) + + def _get_loop(self): + loop = events._get_running_loop() + + if self._loop is None: + with _global_lock: + if self._loop is None: + self._loop = loop + if loop is not self._loop: + raise RuntimeError(f'{self!r} is bound to a different event loop') + return loop diff --git a/Lib/asyncio/queues.py b/Lib/asyncio/queues.py index cd3f7c6a56..a87ec8b215 100644 --- a/Lib/asyncio/queues.py +++ b/Lib/asyncio/queues.py @@ -2,10 +2,9 @@ __all__ = ('Queue', 'PriorityQueue', 'LifoQueue', 'QueueFull', 'QueueEmpty') import collections import heapq -import warnings -from . import events from . import locks +from . import mixins class QueueEmpty(Exception): @@ -18,7 +17,7 @@ class QueueFull(Exception): pass -class Queue: +class Queue(mixins._LoopBoundMixin): """A queue, useful for coordinating producer and consumer coroutines. If maxsize is less than or equal to zero, the queue size is infinite. If it @@ -30,14 +29,8 @@ class Queue: interrupted between calling qsize() and doing an operation on the Queue. """ - def __init__(self, maxsize=0, *, loop=None): - if loop is None: - self._loop = events.get_event_loop() - else: - self._loop = loop - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) + def __init__(self, maxsize=0, *, loop=mixins._marker): + super().__init__(loop=loop) self._maxsize = maxsize # Futures. @@ -45,7 +38,7 @@ class Queue: # Futures. self._putters = collections.deque() self._unfinished_tasks = 0 - self._finished = locks.Event(loop=loop) + self._finished = locks.Event() self._finished.set() self._init(maxsize) @@ -122,7 +115,7 @@ class Queue: slot is available before adding item. """ while self.full(): - putter = self._loop.create_future() + putter = self._get_loop().create_future() self._putters.append(putter) try: await putter @@ -160,7 +153,7 @@ class Queue: If queue is empty, wait until an item is available. """ while self.empty(): - getter = self._loop.create_future() + getter = self._get_loop().create_future() self._getters.append(getter) try: await getter diff --git a/Lib/asyncio/runners.py b/Lib/asyncio/runners.py index 268635d68f..9a5e9a4847 100644 --- a/Lib/asyncio/runners.py +++ b/Lib/asyncio/runners.py @@ -60,8 +60,7 @@ def _cancel_all_tasks(loop): for task in to_cancel: task.cancel() - loop.run_until_complete( - tasks.gather(*to_cancel, loop=loop, return_exceptions=True)) + loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True)) for task in to_cancel: if task.cancelled(): diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index 3c80bb8892..96a9f97200 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -23,7 +23,7 @@ _DEFAULT_LIMIT = 2 ** 16 # 64 KiB async def open_connection(host=None, port=None, *, - loop=None, limit=_DEFAULT_LIMIT, **kwds): + limit=_DEFAULT_LIMIT, **kwds): """A wrapper for create_connection() returning a (reader, writer) pair. The reader returned is a StreamReader instance; the writer is a @@ -41,12 +41,7 @@ async def open_connection(host=None, port=None, *, StreamReaderProtocol classes, just copy the code -- there's really nothing special here except some convenience.) """ - if loop is None: - loop = events.get_event_loop() - else: - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) + loop = events.get_running_loop() reader = StreamReader(limit=limit, loop=loop) protocol = StreamReaderProtocol(reader, loop=loop) transport, _ = await loop.create_connection( @@ -56,7 +51,7 @@ async def open_connection(host=None, port=None, *, async def start_server(client_connected_cb, host=None, port=None, *, - loop=None, limit=_DEFAULT_LIMIT, **kwds): + limit=_DEFAULT_LIMIT, **kwds): """Start a socket server, call back for each client connected. The first parameter, `client_connected_cb`, takes two parameters: @@ -78,12 +73,7 @@ async def start_server(client_connected_cb, host=None, port=None, *, The return value is the same as loop.create_server(), i.e. a Server object which can be used to stop the service. """ - if loop is None: - loop = events.get_event_loop() - else: - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) + loop = events.get_running_loop() def factory(): reader = StreamReader(limit=limit, loop=loop) @@ -98,14 +88,10 @@ if hasattr(socket, 'AF_UNIX'): # UNIX Domain Sockets are supported on this platform async def open_unix_connection(path=None, *, - loop=None, limit=_DEFAULT_LIMIT, **kwds): + limit=_DEFAULT_LIMIT, **kwds): """Similar to `open_connection` but works with UNIX Domain Sockets.""" - if loop is None: - loop = events.get_event_loop() - else: - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) + loop = events.get_running_loop() + reader = StreamReader(limit=limit, loop=loop) protocol = StreamReaderProtocol(reader, loop=loop) transport, _ = await loop.create_unix_connection( @@ -114,14 +100,9 @@ if hasattr(socket, 'AF_UNIX'): return reader, writer async def start_unix_server(client_connected_cb, path=None, *, - loop=None, limit=_DEFAULT_LIMIT, **kwds): + limit=_DEFAULT_LIMIT, **kwds): """Similar to `start_server` but works with UNIX Domain Sockets.""" - if loop is None: - loop = events.get_event_loop() - else: - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) + loop = events.get_running_loop() def factory(): reader = StreamReader(limit=limit, loop=loop) diff --git a/Lib/asyncio/subprocess.py b/Lib/asyncio/subprocess.py index c9506b1583..cd10231f71 100644 --- a/Lib/asyncio/subprocess.py +++ b/Lib/asyncio/subprocess.py @@ -1,7 +1,6 @@ __all__ = 'create_subprocess_exec', 'create_subprocess_shell' import subprocess -import warnings from . import events from . import protocols @@ -193,24 +192,14 @@ class Process: stderr = self._read_stream(2) else: stderr = self._noop() - stdin, stdout, stderr = await tasks.gather(stdin, stdout, stderr, - loop=self._loop) + stdin, stdout, stderr = await tasks.gather(stdin, stdout, stderr) await self.wait() return (stdout, stderr) async def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None, - loop=None, limit=streams._DEFAULT_LIMIT, - **kwds): - if loop is None: - loop = events.get_event_loop() - else: - warnings.warn("The loop argument is deprecated since Python 3.8 " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, - stacklevel=2 - ) - + limit=streams._DEFAULT_LIMIT, **kwds): + loop = events.get_running_loop() protocol_factory = lambda: SubprocessStreamProtocol(limit=limit, loop=loop) transport, protocol = await loop.subprocess_shell( @@ -221,16 +210,9 @@ async def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None, async def create_subprocess_exec(program, *args, stdin=None, stdout=None, - stderr=None, loop=None, - limit=streams._DEFAULT_LIMIT, **kwds): - if loop is None: - loop = events.get_event_loop() - else: - warnings.warn("The loop argument is deprecated since Python 3.8 " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, - stacklevel=2 - ) + stderr=None, limit=streams._DEFAULT_LIMIT, + **kwds): + loop = events.get_running_loop() protocol_factory = lambda: SubprocessStreamProtocol(limit=limit, loop=loop) transport, protocol = await loop.subprocess_exec( diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index ad31f5d597..52f1e6629e 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -61,30 +61,6 @@ def all_tasks(loop=None): if futures._get_loop(t) is loop and not t.done()} -def _all_tasks_compat(loop=None): - # Different from "all_task()" by returning *all* Tasks, including - # the completed ones. Used to implement deprecated "Tasks.all_task()" - # method. - if loop is None: - loop = events.get_event_loop() - # Looping over a WeakSet (_all_tasks) isn't safe as it can be updated from another - # thread while we do so. Therefore we cast it to list prior to filtering. The list - # cast itself requires iteration, so we repeat it several times ignoring - # RuntimeErrors (which are not very likely to occur). See issues 34970 and 36607 for - # details. - i = 0 - while True: - try: - tasks = list(_all_tasks) - except RuntimeError: - i += 1 - if i >= 1000: - raise - else: - break - return {t for t in tasks if futures._get_loop(t) is loop} - - def _set_task_name(task, name): if name is not None: try: @@ -370,7 +346,7 @@ FIRST_EXCEPTION = concurrent.futures.FIRST_EXCEPTION ALL_COMPLETED = concurrent.futures.ALL_COMPLETED -async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED): +async def wait(fs, *, timeout=None, return_when=ALL_COMPLETED): """Wait for the Futures and coroutines given by fs to complete. The fs iterable must not be empty. @@ -393,20 +369,17 @@ async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED): if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED): raise ValueError(f'Invalid return_when value: {return_when}') - if loop is None: - loop = events.get_running_loop() - else: - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) + loop = events.get_running_loop() + + fs = set(fs) - if any(coroutines.iscoroutine(f) for f in set(fs)): + if any(coroutines.iscoroutine(f) for f in fs): warnings.warn("The explicit passing of coroutine objects to " "asyncio.wait() is deprecated since Python 3.8, and " "scheduled for removal in Python 3.11.", DeprecationWarning, stacklevel=2) - fs = {ensure_future(f, loop=loop) for f in set(fs)} + fs = {ensure_future(f, loop=loop) for f in fs} return await _wait(fs, timeout, return_when, loop) @@ -416,7 +389,7 @@ def _release_waiter(waiter, *args): waiter.set_result(None) -async def wait_for(fut, timeout, *, loop=None): +async def wait_for(fut, timeout): """Wait for the single Future or coroutine to complete, with timeout. Coroutine will be wrapped in Task. @@ -429,12 +402,7 @@ async def wait_for(fut, timeout, *, loop=None): This function is a coroutine. """ - if loop is None: - loop = events.get_running_loop() - else: - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) + loop = events.get_running_loop() if timeout is None: return await fut @@ -469,7 +437,10 @@ async def wait_for(fut, timeout, *, loop=None): return fut.result() else: fut.remove_done_callback(cb) - fut.cancel() + # We must ensure that the task is not running + # after wait_for() returns. + # See https://bugs.python.org/issue32751 + await _cancel_and_wait(fut, loop=loop) raise if fut.done(): @@ -554,7 +525,7 @@ async def _cancel_and_wait(fut, loop): # This is *not* a @coroutine! It is just an iterator (yielding Futures). -def as_completed(fs, *, loop=None, timeout=None): +def as_completed(fs, *, timeout=None): """Return an iterator whose values are coroutines. When waiting for the yielded coroutines you'll get the results (or @@ -576,14 +547,9 @@ def as_completed(fs, *, loop=None, timeout=None): raise TypeError(f"expect an iterable of futures, not {type(fs).__name__}") from .queues import Queue # Import here to avoid circular import problem. - done = Queue(loop=loop) + done = Queue() - if loop is None: - loop = events.get_event_loop() - else: - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) + loop = events.get_event_loop() todo = {ensure_future(f, loop=loop) for f in set(fs)} timeout_handle = None @@ -628,19 +594,13 @@ def __sleep0(): yield -async def sleep(delay, result=None, *, loop=None): +async def sleep(delay, result=None): """Coroutine that completes after a given time (in seconds).""" if delay <= 0: await __sleep0() return result - if loop is None: - loop = events.get_running_loop() - else: - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) - + loop = events.get_running_loop() future = loop.create_future() h = loop.call_later(delay, futures._set_result_unless_cancelled, @@ -715,7 +675,7 @@ class _GatheringFuture(futures.Future): return ret -def gather(*coros_or_futures, loop=None, return_exceptions=False): +def gather(*coros_or_futures, return_exceptions=False): """Return a future aggregating results from the given coroutines/futures. Coroutines will be wrapped in a future and scheduled in the event @@ -746,12 +706,7 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False): gather won't cancel any other awaitables. """ if not coros_or_futures: - if loop is None: - loop = events.get_event_loop() - else: - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) + loop = events.get_event_loop() outer = loop.create_future() outer.set_result([]) return outer @@ -815,6 +770,7 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False): children = [] nfuts = 0 nfinished = 0 + loop = None for arg in coros_or_futures: if arg not in arg_to_fut: fut = ensure_future(arg, loop=loop) @@ -841,7 +797,7 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False): return outer -def shield(arg, *, loop=None): +def shield(arg): """Wait for a future, shielding it from cancellation. The statement @@ -867,11 +823,7 @@ def shield(arg, *, loop=None): except CancelledError: res = None """ - if loop is not None: - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) - inner = ensure_future(arg, loop=loop) + inner = ensure_future(arg) if inner.done(): # Shortcut. return inner diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py index f34a5b4b44..e4f445e950 100644 --- a/Lib/asyncio/unix_events.py +++ b/Lib/asyncio/unix_events.py @@ -44,6 +44,16 @@ def _sighandler_noop(signum, frame): pass +def waitstatus_to_exitcode(status): + try: + return os.waitstatus_to_exitcode(status) + except ValueError: + # The child exited, but we don't understand its status. + # This shouldn't happen, but if it does, let's just + # return that status; perhaps that helps debug it. + return status + + class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop): """Unix event loop. @@ -323,7 +333,7 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop): server._start_serving() # Skip one loop iteration so that all 'loop.add_reader' # go through. - await tasks.sleep(0, loop=self) + await tasks.sleep(0) return server @@ -941,7 +951,7 @@ class PidfdChildWatcher(AbstractChildWatcher): " will report returncode 255", pid) else: - returncode = _compute_returncode(status) + returncode = waitstatus_to_exitcode(status) os.close(pidfd) callback(pid, returncode, *args) @@ -956,20 +966,6 @@ class PidfdChildWatcher(AbstractChildWatcher): return True -def _compute_returncode(status): - if os.WIFSIGNALED(status): - # The child process died because of a signal. - return -os.WTERMSIG(status) - elif os.WIFEXITED(status): - # The child process exited (e.g sys.exit()). - return os.WEXITSTATUS(status) - else: - # The child exited, but we don't understand its status. - # This shouldn't happen, but if it does, let's just - # return that status; perhaps that helps debug it. - return status - - class BaseChildWatcher(AbstractChildWatcher): def __init__(self): @@ -1080,7 +1076,7 @@ class SafeChildWatcher(BaseChildWatcher): # The child process is still alive. return - returncode = _compute_returncode(status) + returncode = waitstatus_to_exitcode(status) if self._loop.get_debug(): logger.debug('process %s exited with returncode %s', expected_pid, returncode) @@ -1173,7 +1169,7 @@ class FastChildWatcher(BaseChildWatcher): # A child process is still alive. return - returncode = _compute_returncode(status) + returncode = waitstatus_to_exitcode(status) with self._lock: try: @@ -1230,13 +1226,15 @@ class MultiLoopChildWatcher(AbstractChildWatcher): def close(self): self._callbacks.clear() - if self._saved_sighandler is not None: - handler = signal.getsignal(signal.SIGCHLD) - if handler != self._sig_chld: - logger.warning("SIGCHLD handler was changed by outside code") - else: - signal.signal(signal.SIGCHLD, self._saved_sighandler) - self._saved_sighandler = None + if self._saved_sighandler is None: + return + + handler = signal.getsignal(signal.SIGCHLD) + if handler != self._sig_chld: + logger.warning("SIGCHLD handler was changed by outside code") + else: + signal.signal(signal.SIGCHLD, self._saved_sighandler) + self._saved_sighandler = None def __enter__(self): return self @@ -1263,15 +1261,17 @@ class MultiLoopChildWatcher(AbstractChildWatcher): # The reason to do it here is that attach_loop() is called from # unix policy only for the main thread. # Main thread is required for subscription on SIGCHLD signal + if self._saved_sighandler is not None: + return + + self._saved_sighandler = signal.signal(signal.SIGCHLD, self._sig_chld) if self._saved_sighandler is None: - self._saved_sighandler = signal.signal(signal.SIGCHLD, self._sig_chld) - if self._saved_sighandler is None: - logger.warning("Previous SIGCHLD handler was set by non-Python code, " - "restore to default handler on watcher close.") - self._saved_sighandler = signal.SIG_DFL + logger.warning("Previous SIGCHLD handler was set by non-Python code, " + "restore to default handler on watcher close.") + self._saved_sighandler = signal.SIG_DFL - # Set SA_RESTART to limit EINTR occurrences. - signal.siginterrupt(signal.SIGCHLD, False) + # Set SA_RESTART to limit EINTR occurrences. + signal.siginterrupt(signal.SIGCHLD, False) def _do_waitpid_all(self): for pid in list(self._callbacks): @@ -1296,7 +1296,7 @@ class MultiLoopChildWatcher(AbstractChildWatcher): # The child process is still alive. return - returncode = _compute_returncode(status) + returncode = waitstatus_to_exitcode(status) debug_log = True try: loop, callback, args = self._callbacks.pop(pid) @@ -1399,7 +1399,7 @@ class ThreadedChildWatcher(AbstractChildWatcher): "Unknown child process pid %d, will report returncode 255", pid) else: - returncode = _compute_returncode(status) + returncode = waitstatus_to_exitcode(status) if loop.get_debug(): logger.debug('process %s exited with returncode %s', expected_pid, returncode) diff --git a/Lib/asyncore.py b/Lib/asyncore.py index ce16f11a2f..eeea488886 100644 --- a/Lib/asyncore.py +++ b/Lib/asyncore.py @@ -113,7 +113,7 @@ def readwrite(obj, flags): if flags & (select.POLLHUP | select.POLLERR | select.POLLNVAL): obj.handle_close() except OSError as e: - if e.args[0] not in _DISCONNECTED: + if e.errno not in _DISCONNECTED: obj.handle_error() else: obj.handle_close() @@ -236,7 +236,7 @@ class dispatcher: try: self.addr = sock.getpeername() except OSError as err: - if err.args[0] in (ENOTCONN, EINVAL): + if err.errno in (ENOTCONN, EINVAL): # To handle the case where we got an unconnected # socket. self.connected = False @@ -346,7 +346,7 @@ class dispatcher: except TypeError: return None except OSError as why: - if why.args[0] in (EWOULDBLOCK, ECONNABORTED, EAGAIN): + if why.errno in (EWOULDBLOCK, ECONNABORTED, EAGAIN): return None else: raise @@ -358,9 +358,9 @@ class dispatcher: result = self.socket.send(data) return result except OSError as why: - if why.args[0] == EWOULDBLOCK: + if why.errno == EWOULDBLOCK: return 0 - elif why.args[0] in _DISCONNECTED: + elif why.errno in _DISCONNECTED: self.handle_close() return 0 else: @@ -378,7 +378,7 @@ class dispatcher: return data except OSError as why: # winsock sometimes raises ENOTCONN - if why.args[0] in _DISCONNECTED: + if why.errno in _DISCONNECTED: self.handle_close() return b'' else: @@ -393,7 +393,7 @@ class dispatcher: try: self.socket.close() except OSError as why: - if why.args[0] not in (ENOTCONN, EBADF): + if why.errno not in (ENOTCONN, EBADF): raise # log and log_info may be overridden to provide more sophisticated @@ -557,7 +557,7 @@ def close_all(map=None, ignore_all=False): try: x.close() except OSError as x: - if x.args[0] == EBADF: + if x.errno == EBADF: pass elif not ignore_all: raise diff --git a/Lib/cgi.py b/Lib/cgi.py index 77ab703cc0..6018c36086 100755 --- a/Lib/cgi.py +++ b/Lib/cgi.py @@ -194,7 +194,7 @@ def parse_multipart(fp, pdict, encoding="utf-8", errors="replace"): value is a list of values for that field. For non-file fields, the value is a list of strings. """ - # RFC 2026, Section 5.1 : The "multipart" boundary delimiters are always + # RFC 2046, Section 5.1 : The "multipart" boundary delimiters are always # represented as 7bit US-ASCII. boundary = pdict['boundary'].decode('ascii') ctype = "multipart/form-data; boundary={}".format(boundary) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 5d75501645..9c25a2d278 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -1001,7 +1001,7 @@ class ChainMap(_collections_abc.MutableMapping): def __iter__(self): d = {} for mapping in reversed(self.maps): - d.update(mapping) # reuses stored hash values if possible + d.update(dict.fromkeys(mapping)) # reuses stored hash values if possible return iter(d) def __contains__(self, key): diff --git a/Lib/collections/abc.py b/Lib/collections/abc.py index 891600d16b..86ca8b8a84 100644 --- a/Lib/collections/abc.py +++ b/Lib/collections/abc.py @@ -1,2 +1,3 @@ from _collections_abc import * from _collections_abc import __all__ +from _collections_abc import _CallableGenericAlias diff --git a/Lib/colorsys.py b/Lib/colorsys.py index b93e384406..0f52512a67 100644 --- a/Lib/colorsys.py +++ b/Lib/colorsys.py @@ -75,17 +75,18 @@ def yiq_to_rgb(y, i, q): def rgb_to_hls(r, g, b): maxc = max(r, g, b) minc = min(r, g, b) - # XXX Can optimize (maxc+minc) and (maxc-minc) - l = (minc+maxc)/2.0 + sumc = (maxc+minc) + rangec = (maxc-minc) + l = sumc/2.0 if minc == maxc: return 0.0, l, 0.0 if l <= 0.5: - s = (maxc-minc) / (maxc+minc) + s = rangec / sumc else: - s = (maxc-minc) / (2.0-maxc-minc) - rc = (maxc-r) / (maxc-minc) - gc = (maxc-g) / (maxc-minc) - bc = (maxc-b) / (maxc-minc) + s = rangec / (2.0-sumc) + rc = (maxc-r) / rangec + gc = (maxc-g) / rangec + bc = (maxc-b) / rangec if r == maxc: h = bc-gc elif g == maxc: diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 82ddc1497d..eb5946145b 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -9,7 +9,7 @@ from types import MethodType, GenericAlias __all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext", "AbstractContextManager", "AbstractAsyncContextManager", "AsyncExitStack", "ContextDecorator", "ExitStack", - "redirect_stdout", "redirect_stderr", "suppress"] + "redirect_stdout", "redirect_stderr", "suppress", "aclosing"] class AbstractContextManager(abc.ABC): @@ -80,6 +80,22 @@ class ContextDecorator(object): return inner +class AsyncContextDecorator(object): + "A base class or mixin that enables async context managers to work as decorators." + + def _recreate_cm(self): + """Return a recreated instance of self. + """ + return self + + def __call__(self, func): + @wraps(func) + async def inner(*args, **kwds): + async with self._recreate_cm(): + return await func(*args, **kwds) + return inner + + class _GeneratorContextManagerBase: """Shared functionality for @contextmanager and @asynccontextmanager.""" @@ -167,9 +183,16 @@ class _GeneratorContextManager(_GeneratorContextManagerBase, class _AsyncGeneratorContextManager(_GeneratorContextManagerBase, - AbstractAsyncContextManager): + AbstractAsyncContextManager, + AsyncContextDecorator): """Helper for @asynccontextmanager.""" + def _recreate_cm(self): + # _AGCM instances are one-shot context managers, so the + # ACM must be recreated each time a decorated function is + # called + return self.__class__(self.func, self.args, self.kwds) + async def __aenter__(self): try: return await self.gen.__anext__() @@ -681,7 +704,7 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager): return received_exc and suppressed_exc -class nullcontext(AbstractContextManager): +class nullcontext(AbstractContextManager, AbstractAsyncContextManager): """Context manager that does no additional processing. Used as a stand-in for a normal context manager, when a particular @@ -700,3 +723,9 @@ class nullcontext(AbstractContextManager): def __exit__(self, *excinfo): pass + + async def __aenter__(self): + return self.enter_result + + async def __aexit__(self, *excinfo): + pass diff --git a/Lib/ctypes/macholib/dyld.py b/Lib/ctypes/macholib/dyld.py index 9d86b05876..1c3f8fd38b 100644 --- a/Lib/ctypes/macholib/dyld.py +++ b/Lib/ctypes/macholib/dyld.py @@ -6,6 +6,11 @@ import os from ctypes.macholib.framework import framework_info from ctypes.macholib.dylib import dylib_info from itertools import * +try: + from _ctypes import _dyld_shared_cache_contains_path +except ImportError: + def _dyld_shared_cache_contains_path(*args): + raise NotImplementedError __all__ = [ 'dyld_find', 'framework_find', @@ -122,8 +127,15 @@ def dyld_find(name, executable_path=None, env=None): dyld_executable_path_search(name, executable_path), dyld_default_search(name, env), ), env): + if os.path.isfile(path): return path + try: + if _dyld_shared_cache_contains_path(path): + return path + except NotImplementedError: + pass + raise ValueError("dylib %s could not be found" % (name,)) def framework_find(fn, executable_path=None, env=None): diff --git a/Lib/ctypes/test/test_macholib.py b/Lib/ctypes/test/test_macholib.py index 6b3526951a..a1bac26a7d 100644 --- a/Lib/ctypes/test/test_macholib.py +++ b/Lib/ctypes/test/test_macholib.py @@ -45,19 +45,22 @@ def find_lib(name): class MachOTest(unittest.TestCase): @unittest.skipUnless(sys.platform == "darwin", 'OSX-specific test') def test_find(self): - - self.assertEqual(find_lib('pthread'), - '/usr/lib/libSystem.B.dylib') + # On Mac OS 11, system dylibs are only present in the shared cache, + # so symlinks like libpthread.dylib -> libSystem.B.dylib will not + # be resolved by dyld_find + self.assertIn(find_lib('pthread'), + ('/usr/lib/libSystem.B.dylib', '/usr/lib/libpthread.dylib')) result = find_lib('z') # Issue #21093: dyld default search path includes $HOME/lib and # /usr/local/lib before /usr/lib, which caused test failures if # a local copy of libz exists in one of them. Now ignore the head # of the path. - self.assertRegex(result, r".*/lib/libz\..*.*\.dylib") + self.assertRegex(result, r".*/lib/libz.*\.dylib") - self.assertEqual(find_lib('IOKit'), - '/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit') + self.assertIn(find_lib('IOKit'), + ('/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit', + '/System/Library/Frameworks/IOKit.framework/IOKit')) if __name__ == "__main__": unittest.main() diff --git a/Lib/dis.py b/Lib/dis.py index e289e176c7..ccbd65be73 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -384,7 +384,7 @@ def _disassemble_bytes(code, lasti=-1, varnames=None, names=None, constants=None, cells=None, linestarts=None, *, file=None, line_offset=0): # Omit the line number column entirely if we have no line number info - show_lineno = linestarts is not None + show_lineno = bool(linestarts) if show_lineno: maxlineno = max(linestarts.values()) + line_offset if maxlineno >= 1000: @@ -449,32 +449,15 @@ def findlabels(code): def findlinestarts(code): """Find the offsets in a byte code which are start of lines in the source. - Generate pairs (offset, lineno) as described in Python/compile.c. - + Generate pairs (offset, lineno) """ - byte_increments = code.co_lnotab[0::2] - line_increments = code.co_lnotab[1::2] - bytecode_len = len(code.co_code) - - lastlineno = None - lineno = code.co_firstlineno - addr = 0 - for byte_incr, line_incr in zip(byte_increments, line_increments): - if byte_incr: - if lineno != lastlineno: - yield (addr, lineno) - lastlineno = lineno - addr += byte_incr - if addr >= bytecode_len: - # The rest of the lnotab byte offsets are past the end of - # the bytecode, so the lines were optimized away. - return - if line_incr >= 0x80: - # line_increments is an array of 8-bit signed integers - line_incr -= 0x100 - lineno += line_incr - if lineno != lastlineno: - yield (addr, lineno) + lastline = None + for start, end, line in code.co_lines(): + if line is not None and line != lastline: + lastline = line + yield start, line + return + class Bytecode: """The bytecode operations of a piece of code diff --git a/Lib/distutils/command/install.py b/Lib/distutils/command/install.py index aaa300efa9..bdead133bd 100644 --- a/Lib/distutils/command/install.py +++ b/Lib/distutils/command/install.py @@ -17,7 +17,8 @@ from distutils.errors import DistutilsOptionError from site import USER_BASE from site import USER_SITE -HAS_USER_SITE = True + +HAS_USER_SITE = (USER_SITE is not None) WINDOWS_SCHEME = { 'purelib': '$base/Lib/site-packages', @@ -169,8 +170,9 @@ class install(Command): self.install_lib = None # set to either purelib or platlib self.install_scripts = None self.install_data = None - self.install_userbase = USER_BASE - self.install_usersite = USER_SITE + if HAS_USER_SITE: + self.install_userbase = USER_BASE + self.install_usersite = USER_SITE self.compile = None self.optimize = None @@ -343,8 +345,9 @@ class install(Command): # Convert directories from Unix /-separated syntax to the local # convention. self.convert_paths('lib', 'purelib', 'platlib', - 'scripts', 'data', 'headers', - 'userbase', 'usersite') + 'scripts', 'data', 'headers') + if HAS_USER_SITE: + self.convert_paths('userbase', 'usersite') # Deprecated # Well, we're not actually fully completely finalized yet: we still diff --git a/Lib/distutils/spawn.py b/Lib/distutils/spawn.py index 0d1bd0391e..f50edd2da9 100644 --- a/Lib/distutils/spawn.py +++ b/Lib/distutils/spawn.py @@ -54,8 +54,8 @@ def spawn(cmd, search_path=1, verbose=0, dry_run=0): global _cfg_target, _cfg_target_split if _cfg_target is None: from distutils import sysconfig - _cfg_target = sysconfig.get_config_var( - 'MACOSX_DEPLOYMENT_TARGET') or '' + _cfg_target = str(sysconfig.get_config_var( + 'MACOSX_DEPLOYMENT_TARGET') or '') if _cfg_target: _cfg_target_split = [int(x) for x in _cfg_target.split('.')] if _cfg_target: diff --git a/Lib/distutils/tests/test_build_ext.py b/Lib/distutils/tests/test_build_ext.py index f9e0d766d8..a3055c1984 100644 --- a/Lib/distutils/tests/test_build_ext.py +++ b/Lib/distutils/tests/test_build_ext.py @@ -456,7 +456,7 @@ class BuildExtTestCase(TempdirManager, deptarget = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET') if deptarget: # increment the minor version number (i.e. 10.6 -> 10.7) - deptarget = [int(x) for x in deptarget.split('.')] + deptarget = [int(x) for x in str(deptarget).split('.')] deptarget[-1] += 1 deptarget = '.'.join(str(i) for i in deptarget) self._try_compile_deployment_target('<', deptarget) @@ -489,16 +489,20 @@ class BuildExtTestCase(TempdirManager, # get the deployment target that the interpreter was built with target = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET') - target = tuple(map(int, target.split('.')[0:2])) + target = tuple(map(int, str(target).split('.')[0:2])) # format the target value as defined in the Apple # Availability Macros. We can't use the macro names since # at least one value we test with will not exist yet. - if target[1] < 10: + if target[:2] < (10, 10): # for 10.1 through 10.9.x -> "10n0" target = '%02d%01d0' % target else: # for 10.10 and beyond -> "10nn00" - target = '%02d%02d00' % target + if len(target) >= 2: + target = '%02d%02d00' % target + else: + # 11 and later can have no minor version (11 instead of 11.0) + target = '%02d0000' % target deptarget_ext = Extension( 'deptarget', [deptarget_c], diff --git a/Lib/distutils/tests/test_install.py b/Lib/distutils/tests/test_install.py index 51c80e0421..21a7b7c85c 100644 --- a/Lib/distutils/tests/test_install.py +++ b/Lib/distutils/tests/test_install.py @@ -8,7 +8,7 @@ import site from test.support import captured_stdout, run_unittest from distutils import sysconfig -from distutils.command.install import install +from distutils.command.install import install, HAS_USER_SITE from distutils.command import install as install_module from distutils.command.build_ext import build_ext from distutils.command.install import INSTALL_SCHEMES @@ -66,6 +66,7 @@ class InstallTestCase(support.TempdirManager, check_path(cmd.install_scripts, os.path.join(destination, "bin")) check_path(cmd.install_data, destination) + @unittest.skipUnless(HAS_USER_SITE, 'need user site') def test_user_site(self): # test install with --user # preparing the environment for the test @@ -93,8 +94,9 @@ class InstallTestCase(support.TempdirManager, self.addCleanup(cleanup) - for key in ('nt_user', 'unix_user'): - self.assertIn(key, INSTALL_SCHEMES) + if HAS_USER_SITE: + for key in ('nt_user', 'unix_user'): + self.assertIn(key, INSTALL_SCHEMES) dist = Distribution({'name': 'xx'}) cmd = install(dist) diff --git a/Lib/distutils/unixccompiler.py b/Lib/distutils/unixccompiler.py index 4d7a6de740..f0792de74a 100644 --- a/Lib/distutils/unixccompiler.py +++ b/Lib/distutils/unixccompiler.py @@ -290,7 +290,7 @@ class UnixCCompiler(CCompiler): cflags = sysconfig.get_config_var('CFLAGS') m = re.search(r'-isysroot\s*(\S+)', cflags) if m is None: - sysroot = '/' + sysroot = _osx_support._default_sysroot(sysconfig.get_config_var('CC')) else: sysroot = m.group(1) diff --git a/Lib/doctest.py b/Lib/doctest.py index baa503c83f..5bb35c9715 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -222,13 +222,17 @@ def _load_testfile(filename, package, module_relative, encoding): if module_relative: package = _normalize_module(package, 3) filename = _module_relative_path(package, filename) - if getattr(package, '__loader__', None) is not None: - if hasattr(package.__loader__, 'get_data'): - file_contents = package.__loader__.get_data(filename) - file_contents = file_contents.decode(encoding) - # get_data() opens files as 'rb', so one must do the equivalent - # conversion as universal newlines would do. - return _newline_convert(file_contents), filename + if (loader := getattr(package, '__loader__', None)) is None: + try: + loader = package.__spec__.loader + except AttributeError: + pass + if hasattr(loader, 'get_data'): + file_contents = loader.get_data(filename) + file_contents = file_contents.decode(encoding) + # get_data() opens files as 'rb', so one must do the equivalent + # conversion as universal newlines would do. + return _newline_convert(file_contents), filename with open(filename, encoding=encoding) as f: return f.read(), filename diff --git a/Lib/enum.py b/Lib/enum.py index 40ff25b9cd..75249bfdc1 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -9,32 +9,63 @@ __all__ = [ ] +class _NoInitSubclass: + """ + temporary base class to suppress calling __init_subclass__ + """ + @classmethod + def __init_subclass__(cls, **kwds): + pass + def _is_descriptor(obj): - """Returns True if obj is a descriptor, False otherwise.""" + """ + Returns True if obj is a descriptor, False otherwise. + """ return ( hasattr(obj, '__get__') or hasattr(obj, '__set__') or - hasattr(obj, '__delete__')) - + hasattr(obj, '__delete__') + ) def _is_dunder(name): - """Returns True if a __dunder__ name, False otherwise.""" - return (len(name) > 4 and + """ + Returns True if a __dunder__ name, False otherwise. + """ + return ( + len(name) > 4 and name[:2] == name[-2:] == '__' and name[2] != '_' and - name[-3] != '_') - + name[-3] != '_' + ) def _is_sunder(name): - """Returns True if a _sunder_ name, False otherwise.""" - return (len(name) > 2 and + """ + Returns True if a _sunder_ name, False otherwise. + """ + return ( + len(name) > 2 and name[0] == name[-1] == '_' and name[1:2] != '_' and - name[-2:-1] != '_') - + name[-2:-1] != '_' + ) + +def _is_private(cls_name, name): + # do not use `re` as `re` imports `enum` + pattern = '_%s__' % (cls_name, ) + if ( + len(name) >= 5 + and name.startswith(pattern) + and name[len(pattern)] != '_' + and (name[-1] != '_' or name[-2] != '_') + ): + return True + else: + return False def _make_class_unpicklable(cls): - """Make the given class un-picklable.""" + """ + Make the given class un-picklable. + """ def _break_on_call_reduce(self, proto): raise TypeError('%r cannot be pickled' % self) cls.__reduce_ex__ = _break_on_call_reduce @@ -49,11 +80,11 @@ class auto: class _EnumDict(dict): - """Track enum member order and ensure member names are not reused. + """ + Track enum member order and ensure member names are not reused. EnumMeta will use the names found in self._member_names as the enumeration member names. - """ def __init__(self): super().__init__() @@ -63,21 +94,26 @@ class _EnumDict(dict): self._auto_called = False def __setitem__(self, key, value): - """Changes anything not dundered or not a descriptor. + """ + Changes anything not dundered or not a descriptor. If an enum member name is used twice, an error is raised; duplicate values are not checked for. Single underscore (sunder) names are reserved. - """ - if _is_sunder(key): + if _is_private(self._cls_name, key): + # do nothing, name will be a normal attribute + pass + elif _is_sunder(key): if key not in ( '_order_', '_create_pseudo_member_', '_generate_next_value_', '_missing_', '_ignore_', ): - raise ValueError(f'_sunder_ names, such as "{key}", are ' - 'reserved for future Enum use') + raise ValueError( + '_sunder_ names, such as %r, are reserved for future Enum use' + % (key, ) + ) if key == '_generate_next_value_': # check if members already defined as auto() if self._auto_called: @@ -91,13 +127,16 @@ class _EnumDict(dict): self._ignore = value already = set(value) & set(self._member_names) if already: - raise ValueError('_ignore_ cannot specify already set names: %r' % (already, )) + raise ValueError( + '_ignore_ cannot specify already set names: %r' + % (already, ) + ) elif _is_dunder(key): if key == '__order__': key = '_order_' elif key in self._member_names: # descriptor overwriting an enum? - raise TypeError('Attempted to reuse key: %r' % key) + raise TypeError('%r already defined as: %r' % (key, self[key])) elif key in self._ignore: pass elif not _is_descriptor(value): @@ -106,35 +145,54 @@ class _EnumDict(dict): raise TypeError('%r already defined as: %r' % (key, self[key])) if isinstance(value, auto): if value.value == _auto_null: - value.value = self._generate_next_value(key, 1, len(self._member_names), self._last_values[:]) + value.value = self._generate_next_value( + key, + 1, + len(self._member_names), + self._last_values[:], + ) self._auto_called = True value = value.value self._member_names.append(key) self._last_values.append(value) super().__setitem__(key, value) + def update(self, members, **more_members): + try: + for name in members.keys(): + self[name] = members[name] + except AttributeError: + for name, value in members: + self[name] = value + for name, value in more_members.items(): + self[name] = value + # Dummy value for Enum as EnumMeta explicitly checks for it, but of course # until EnumMeta finishes running the first time the Enum class doesn't exist. # This is also why there are checks in EnumMeta like `if Enum is not None` Enum = None - class EnumMeta(type): - """Metaclass for Enum""" + """ + Metaclass for Enum + """ @classmethod - def __prepare__(metacls, cls, bases): + def __prepare__(metacls, cls, bases, **kwds): # check that previous enum members do not exist metacls._check_for_existing_members(cls, bases) # create the namespace dict enum_dict = _EnumDict() + enum_dict._cls_name = cls # inherit previous flags and _generate_next_value_ function member_type, first_enum = metacls._get_mixins_(cls, bases) if first_enum is not None: - enum_dict['_generate_next_value_'] = getattr(first_enum, '_generate_next_value_', None) + enum_dict['_generate_next_value_'] = getattr( + first_enum, '_generate_next_value_', None, + ) return enum_dict - def __new__(metacls, cls, bases, classdict): + def __new__(metacls, cls, bases, classdict, **kwds): # an Enum class is final once enumeration items have been defined; it # cannot be mixed with other types (int, float, etc.) if it has an # inherited __new__ unless a new __new__ is defined (or the resulting @@ -146,8 +204,9 @@ class EnumMeta(type): for key in ignore: classdict.pop(key, None) member_type, first_enum = metacls._get_mixins_(cls, bases) - __new__, save_new, use_args = metacls._find_new_(classdict, member_type, - first_enum) + __new__, save_new, use_args = metacls._find_new_( + classdict, member_type, first_enum, + ) # save enum items into separate mapping so they don't get baked into # the new class @@ -168,17 +227,33 @@ class EnumMeta(type): if '__doc__' not in classdict: classdict['__doc__'] = 'An enumeration.' + # postpone calling __init_subclass__ + if '__init_subclass__' in classdict and classdict['__init_subclass__'] is None: + raise TypeError('%s.__init_subclass__ cannot be None') + # remove current __init_subclass__ so previous one can be found with getattr + new_init_subclass = classdict.pop('__init_subclass__', None) # create our new Enum type - enum_class = super().__new__(metacls, cls, bases, classdict) + if bases: + bases = (_NoInitSubclass, ) + bases + enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) + enum_class.__bases__ = enum_class.__bases__[1:] #or (object, ) + else: + enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) + old_init_subclass = getattr(enum_class, '__init_subclass__', None) + # and restore the new one (if there was one) + if new_init_subclass is not None: + enum_class.__init_subclass__ = classmethod(new_init_subclass) enum_class._member_names_ = [] # names in definition order enum_class._member_map_ = {} # name->value map enum_class._member_type_ = member_type # save DynamicClassAttribute attributes from super classes so we know # if we can take the shortcut of storing members in the class dict - dynamic_attributes = {k for c in enum_class.mro() - for k, v in c.__dict__.items() - if isinstance(v, DynamicClassAttribute)} + dynamic_attributes = { + k for c in enum_class.mro() + for k, v in c.__dict__.items() + if isinstance(v, DynamicClassAttribute) + } # Reverse value->name map for hashable values. enum_class._value2member_map_ = {} @@ -279,6 +354,9 @@ class EnumMeta(type): if _order_ != enum_class._member_names_: raise TypeError('member order does not match _order_') + # finally, call parents' __init_subclass__ + if Enum is not None and old_init_subclass is not None: + old_init_subclass(**kwds) return enum_class def __bool__(self): @@ -288,7 +366,8 @@ class EnumMeta(type): return True def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1): - """Either returns an existing member, or creates a new enum class. + """ + Either returns an existing member, or creates a new enum class. This method is used both when an enum class is given a value to match to an enumeration member (i.e. Color(3)) and for the functional API @@ -310,12 +389,18 @@ class EnumMeta(type): not correct, unpickling will fail in some circumstances. `type`, if set, will be mixed in as the first base class. - """ if names is None: # simple value lookup return cls.__new__(cls, value) # otherwise, functional API: we're creating a new Enum type - return cls._create_(value, names, module=module, qualname=qualname, type=type, start=start) + return cls._create_( + value, + names, + module=module, + qualname=qualname, + type=type, + start=start, + ) def __contains__(cls, member): if not isinstance(member, Enum): @@ -328,22 +413,23 @@ class EnumMeta(type): # nicer error message when someone tries to delete an attribute # (see issue19025). if attr in cls._member_map_: - raise AttributeError( - "%s: cannot delete Enum member." % cls.__name__) + raise AttributeError("%s: cannot delete Enum member %r." % (cls.__name__, attr)) super().__delattr__(attr) def __dir__(self): - return (['__class__', '__doc__', '__members__', '__module__'] + - self._member_names_) + return ( + ['__class__', '__doc__', '__members__', '__module__'] + + self._member_names_ + ) def __getattr__(cls, name): - """Return the enum member matching `name` + """ + Return the enum member matching `name` We use __getattr__ instead of descriptors or inserting into the enum class' __dict__ in order to support `name` and `value` being both properties for enum members (which live in the class' __dict__) and enum members themselves. - """ if _is_dunder(name): raise AttributeError(name) @@ -356,6 +442,9 @@ class EnumMeta(type): return cls._member_map_[name] def __iter__(cls): + """ + Returns members in definition order. + """ return (cls._member_map_[name] for name in cls._member_names_) def __len__(cls): @@ -363,11 +452,11 @@ class EnumMeta(type): @property def __members__(cls): - """Returns a mapping of member name->value. + """ + Returns a mapping of member name->value. This mapping lists all enum members, including aliases. Note that this is a read-only view of the internal mapping. - """ return MappingProxyType(cls._member_map_) @@ -375,15 +464,18 @@ class EnumMeta(type): return "<enum %r>" % cls.__name__ def __reversed__(cls): + """ + Returns members in reverse definition order. + """ return (cls._member_map_[name] for name in reversed(cls._member_names_)) def __setattr__(cls, name, value): - """Block attempts to reassign Enum members. + """ + Block attempts to reassign Enum members. A simple assignment to the class namespace only changes one of the several possible ways to get an Enum member from the Enum class, resulting in an inconsistent Enumeration. - """ member_map = cls.__dict__.get('_member_map_', {}) if name in member_map: @@ -391,7 +483,8 @@ class EnumMeta(type): super().__setattr__(name, value) def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, start=1): - """Convenience method to create a new Enum class. + """ + Convenience method to create a new Enum class. `names` can be: @@ -400,7 +493,6 @@ class EnumMeta(type): * An iterable of member names. Values are incremented by 1 from `start`. * An iterable of (member name, value) pairs. * A mapping of member name -> value pairs. - """ metacls = cls.__class__ bases = (cls, ) if type is None else (type, cls) @@ -481,15 +573,18 @@ class EnumMeta(type): for chain in bases: for base in chain.__mro__: if issubclass(base, Enum) and base._member_names_: - raise TypeError("%s: cannot extend enumeration %r" % (class_name, base.__name__)) + raise TypeError( + "%s: cannot extend enumeration %r" + % (class_name, base.__name__) + ) @staticmethod def _get_mixins_(class_name, bases): - """Returns the type for creating enum members, and the first inherited + """ + Returns the type for creating enum members, and the first inherited enum class. bases: the tuple of bases that was given to __new__ - """ if not bases: return object, Enum @@ -501,12 +596,16 @@ class EnumMeta(type): for base in chain.__mro__: if base is object: continue + elif issubclass(base, Enum): + if base._member_type_ is not object: + data_types.append(base._member_type_) + break elif '__new__' in base.__dict__: if issubclass(base, Enum): continue data_types.append(candidate or base) break - elif not issubclass(base, Enum): + else: candidate = base if len(data_types) > 1: raise TypeError('%r: too many data types: %r' % (class_name, data_types)) @@ -528,12 +627,12 @@ class EnumMeta(type): @staticmethod def _find_new_(classdict, member_type, first_enum): - """Returns the __new__ to be used for creating the enum members. + """ + Returns the __new__ to be used for creating the enum members. classdict: the class dictionary given to __new__ member_type: the data type whose __new__ will be used by default first_enum: enumeration to check for an overriding __new__ - """ # now find the correct __new__, checking to see of one was defined # by the user; also check earlier enum classes in case a __new__ was @@ -573,10 +672,10 @@ class EnumMeta(type): class Enum(metaclass=EnumMeta): - """Generic enumeration. + """ + Generic enumeration. Derive from this class to define new enumerations. - """ def __new__(cls, value): # all enum instances are actually created during class construction @@ -619,6 +718,14 @@ class Enum(metaclass=EnumMeta): raise exc def _generate_next_value_(name, start, count, last_values): + """ + Generate the next value when not given. + + name: the name of the member + start: the initial start value or None + count: the number of existing members + last_value: the last value assigned or None + """ for last_value in reversed(last_values): try: return last_value + 1 @@ -627,6 +734,9 @@ class Enum(metaclass=EnumMeta): else: return start + def __init_subclass__(cls, **kwds): + super().__init_subclass__(**kwds) + @classmethod def _missing_(cls, value): return None @@ -639,6 +749,9 @@ class Enum(metaclass=EnumMeta): return "%s.%s" % (self.__class__.__name__, self._name_) def __dir__(self): + """ + Returns all members and all public methods + """ added_behavior = [ m for cls in self.__class__.mro() @@ -648,12 +761,15 @@ class Enum(metaclass=EnumMeta): return (['__class__', '__doc__', '__module__'] + added_behavior) def __format__(self, format_spec): + """ + Returns format using actual value type unless __str__ has been overridden. + """ # mixed-in Enums should use the mixed-in type's __format__, otherwise # we can get strange results with the Enum name showing up instead of # the value # pure Enum branch, or branch with __str__ explicitly overridden - str_overridden = type(self).__str__ != Enum.__str__ + str_overridden = type(self).__str__ not in (Enum.__str__, Flag.__str__) if self._member_type_ is object or str_overridden: cls = str val = str(self) @@ -720,12 +836,20 @@ class StrEnum(str, Enum): __str__ = str.__str__ + def _generate_next_value_(name, start, count, last_values): + """ + Return the lower-cased version of the member name. + """ + return name.lower() + def _reduce_ex_by_name(self, proto): return self.name class Flag(Enum): - """Support for flags""" + """ + Support for flags + """ def _generate_next_value_(name, start, count, last_values): """ @@ -748,6 +872,9 @@ class Flag(Enum): @classmethod def _missing_(cls, value): + """ + Returns member (possibly creating it) if one can be found for value. + """ original_value = value if value < 0: value = ~value @@ -777,6 +904,9 @@ class Flag(Enum): return pseudo_member def __contains__(self, other): + """ + Returns True if self has at least the same flags set as other. + """ if not isinstance(other, self.__class__): raise TypeError( "unsupported operand type(s) for 'in': '%s' and '%s'" % ( @@ -784,6 +914,9 @@ class Flag(Enum): return other._value_ & self._value_ == other._value_ def __iter__(self): + """ + Returns flags in decreasing value order. + """ members, extra_flags = _decompose(self.__class__, self.value) return (m for m in members if m._value_ != 0) @@ -839,10 +972,15 @@ class Flag(Enum): class IntFlag(int, Flag): - """Support for integer-based Flags""" + """ + Support for integer-based Flags + """ @classmethod def _missing_(cls, value): + """ + Returns member (possibly creating it) if one can be found for value. + """ if not isinstance(value, int): raise ValueError("%r is not a valid %s" % (value, cls.__qualname__)) new_member = cls._create_pseudo_member_(value) @@ -850,6 +988,9 @@ class IntFlag(int, Flag): @classmethod def _create_pseudo_member_(cls, value): + """ + Create a composite member iff value contains only members. + """ pseudo_member = cls._value2member_map_.get(value, None) if pseudo_member is None: need_to_create = [value] @@ -904,11 +1045,15 @@ class IntFlag(int, Flag): def _high_bit(value): - """returns index of highest bit, or -1 if value is zero or negative""" + """ + returns index of highest bit, or -1 if value is zero or negative + """ return value.bit_length() - 1 def unique(enumeration): - """Class decorator for enumerations ensuring unique member values.""" + """ + Class decorator for enumerations ensuring unique member values. + """ duplicates = [] for name, member in enumeration.__members__.items(): if name != member.name: @@ -921,7 +1066,9 @@ def unique(enumeration): return enumeration def _decompose(flag, value): - """Extract all members from the value.""" + """ + Extract all members from the value. + """ # _decompose is only called if the value is not named not_covered = value negative = value < 0 diff --git a/Lib/filecmp.py b/Lib/filecmp.py index 7a4da6beb5..7c47eb022c 100644 --- a/Lib/filecmp.py +++ b/Lib/filecmp.py @@ -115,7 +115,9 @@ class dircmp: same_files: list of identical files. diff_files: list of filenames which differ. funny_files: list of files which could not be compared. - subdirs: a dictionary of dircmp objects, keyed by names in common_dirs. + subdirs: a dictionary of dircmp instances (or MyDirCmp instances if this + object is of type MyDirCmp, a subclass of dircmp), keyed by names + in common_dirs. """ def __init__(self, a, b, ignore=None, hide=None): # Initialize @@ -185,14 +187,15 @@ class dircmp: self.same_files, self.diff_files, self.funny_files = xx def phase4(self): # Find out differences between common subdirectories - # A new dircmp object is created for each common subdirectory, + # A new dircmp (or MyDirCmp if dircmp was subclassed) object is created + # for each common subdirectory, # these are stored in a dictionary indexed by filename. # The hide and ignore properties are inherited from the parent self.subdirs = {} for x in self.common_dirs: a_x = os.path.join(self.left, x) b_x = os.path.join(self.right, x) - self.subdirs[x] = dircmp(a_x, b_x, self.ignore, self.hide) + self.subdirs[x] = self.__class__(a_x, b_x, self.ignore, self.hide) def phase4_closure(self): # Recursively call phase4() on subdirectories self.phase4() diff --git a/Lib/fnmatch.py b/Lib/fnmatch.py index 0eb1802bdb..7c52c23067 100644 --- a/Lib/fnmatch.py +++ b/Lib/fnmatch.py @@ -52,7 +52,7 @@ def _compile_pattern(pat): return re.compile(res).match def filter(names, pat): - """Return the subset of the list NAMES that match PAT.""" + """Construct a list from those elements of the iterable NAMES that match PAT.""" result = [] pat = os.path.normcase(pat) match = _compile_pattern(pat) diff --git a/Lib/formatter.py b/Lib/formatter.py deleted file mode 100644 index e2394de8c2..0000000000 --- a/Lib/formatter.py +++ /dev/null @@ -1,452 +0,0 @@ -"""Generic output formatting. - -Formatter objects transform an abstract flow of formatting events into -specific output events on writer objects. Formatters manage several stack -structures to allow various properties of a writer object to be changed and -restored; writers need not be able to handle relative changes nor any sort -of ``change back'' operation. Specific writer properties which may be -controlled via formatter objects are horizontal alignment, font, and left -margin indentations. A mechanism is provided which supports providing -arbitrary, non-exclusive style settings to a writer as well. Additional -interfaces facilitate formatting events which are not reversible, such as -paragraph separation. - -Writer objects encapsulate device interfaces. Abstract devices, such as -file formats, are supported as well as physical devices. The provided -implementations all work with abstract devices. The interface makes -available mechanisms for setting the properties which formatter objects -manage and inserting data into the output. -""" - -import sys -import warnings -warnings.warn('the formatter module is deprecated', DeprecationWarning, - stacklevel=2) - - -AS_IS = None - - -class NullFormatter: - """A formatter which does nothing. - - If the writer parameter is omitted, a NullWriter instance is created. - No methods of the writer are called by NullFormatter instances. - - Implementations should inherit from this class if implementing a writer - interface but don't need to inherit any implementation. - - """ - - def __init__(self, writer=None): - if writer is None: - writer = NullWriter() - self.writer = writer - def end_paragraph(self, blankline): pass - def add_line_break(self): pass - def add_hor_rule(self, *args, **kw): pass - def add_label_data(self, format, counter, blankline=None): pass - def add_flowing_data(self, data): pass - def add_literal_data(self, data): pass - def flush_softspace(self): pass - def push_alignment(self, align): pass - def pop_alignment(self): pass - def push_font(self, x): pass - def pop_font(self): pass - def push_margin(self, margin): pass - def pop_margin(self): pass - def set_spacing(self, spacing): pass - def push_style(self, *styles): pass - def pop_style(self, n=1): pass - def assert_line_data(self, flag=1): pass - - -class AbstractFormatter: - """The standard formatter. - - This implementation has demonstrated wide applicability to many writers, - and may be used directly in most circumstances. It has been used to - implement a full-featured World Wide Web browser. - - """ - - # Space handling policy: blank spaces at the boundary between elements - # are handled by the outermost context. "Literal" data is not checked - # to determine context, so spaces in literal data are handled directly - # in all circumstances. - - def __init__(self, writer): - self.writer = writer # Output device - self.align = None # Current alignment - self.align_stack = [] # Alignment stack - self.font_stack = [] # Font state - self.margin_stack = [] # Margin state - self.spacing = None # Vertical spacing state - self.style_stack = [] # Other state, e.g. color - self.nospace = 1 # Should leading space be suppressed - self.softspace = 0 # Should a space be inserted - self.para_end = 1 # Just ended a paragraph - self.parskip = 0 # Skipped space between paragraphs? - self.hard_break = 1 # Have a hard break - self.have_label = 0 - - def end_paragraph(self, blankline): - if not self.hard_break: - self.writer.send_line_break() - self.have_label = 0 - if self.parskip < blankline and not self.have_label: - self.writer.send_paragraph(blankline - self.parskip) - self.parskip = blankline - self.have_label = 0 - self.hard_break = self.nospace = self.para_end = 1 - self.softspace = 0 - - def add_line_break(self): - if not (self.hard_break or self.para_end): - self.writer.send_line_break() - self.have_label = self.parskip = 0 - self.hard_break = self.nospace = 1 - self.softspace = 0 - - def add_hor_rule(self, *args, **kw): - if not self.hard_break: - self.writer.send_line_break() - self.writer.send_hor_rule(*args, **kw) - self.hard_break = self.nospace = 1 - self.have_label = self.para_end = self.softspace = self.parskip = 0 - - def add_label_data(self, format, counter, blankline = None): - if self.have_label or not self.hard_break: - self.writer.send_line_break() - if not self.para_end: - self.writer.send_paragraph((blankline and 1) or 0) - if isinstance(format, str): - self.writer.send_label_data(self.format_counter(format, counter)) - else: - self.writer.send_label_data(format) - self.nospace = self.have_label = self.hard_break = self.para_end = 1 - self.softspace = self.parskip = 0 - - def format_counter(self, format, counter): - label = '' - for c in format: - if c == '1': - label = label + ('%d' % counter) - elif c in 'aA': - if counter > 0: - label = label + self.format_letter(c, counter) - elif c in 'iI': - if counter > 0: - label = label + self.format_roman(c, counter) - else: - label = label + c - return label - - def format_letter(self, case, counter): - label = '' - while counter > 0: - counter, x = divmod(counter-1, 26) - # This makes a strong assumption that lowercase letters - # and uppercase letters form two contiguous blocks, with - # letters in order! - s = chr(ord(case) + x) - label = s + label - return label - - def format_roman(self, case, counter): - ones = ['i', 'x', 'c', 'm'] - fives = ['v', 'l', 'd'] - label, index = '', 0 - # This will die of IndexError when counter is too big - while counter > 0: - counter, x = divmod(counter, 10) - if x == 9: - label = ones[index] + ones[index+1] + label - elif x == 4: - label = ones[index] + fives[index] + label - else: - if x >= 5: - s = fives[index] - x = x-5 - else: - s = '' - s = s + ones[index]*x - label = s + label - index = index + 1 - if case == 'I': - return label.upper() - return label - - def add_flowing_data(self, data): - if not data: return - prespace = data[:1].isspace() - postspace = data[-1:].isspace() - data = " ".join(data.split()) - if self.nospace and not data: - return - elif prespace or self.softspace: - if not data: - if not self.nospace: - self.softspace = 1 - self.parskip = 0 - return - if not self.nospace: - data = ' ' + data - self.hard_break = self.nospace = self.para_end = \ - self.parskip = self.have_label = 0 - self.softspace = postspace - self.writer.send_flowing_data(data) - - def add_literal_data(self, data): - if not data: return - if self.softspace: - self.writer.send_flowing_data(" ") - self.hard_break = data[-1:] == '\n' - self.nospace = self.para_end = self.softspace = \ - self.parskip = self.have_label = 0 - self.writer.send_literal_data(data) - - def flush_softspace(self): - if self.softspace: - self.hard_break = self.para_end = self.parskip = \ - self.have_label = self.softspace = 0 - self.nospace = 1 - self.writer.send_flowing_data(' ') - - def push_alignment(self, align): - if align and align != self.align: - self.writer.new_alignment(align) - self.align = align - self.align_stack.append(align) - else: - self.align_stack.append(self.align) - - def pop_alignment(self): - if self.align_stack: - del self.align_stack[-1] - if self.align_stack: - self.align = align = self.align_stack[-1] - self.writer.new_alignment(align) - else: - self.align = None - self.writer.new_alignment(None) - - def push_font(self, font): - size, i, b, tt = font - if self.softspace: - self.hard_break = self.para_end = self.softspace = 0 - self.nospace = 1 - self.writer.send_flowing_data(' ') - if self.font_stack: - csize, ci, cb, ctt = self.font_stack[-1] - if size is AS_IS: size = csize - if i is AS_IS: i = ci - if b is AS_IS: b = cb - if tt is AS_IS: tt = ctt - font = (size, i, b, tt) - self.font_stack.append(font) - self.writer.new_font(font) - - def pop_font(self): - if self.font_stack: - del self.font_stack[-1] - if self.font_stack: - font = self.font_stack[-1] - else: - font = None - self.writer.new_font(font) - - def push_margin(self, margin): - self.margin_stack.append(margin) - fstack = [m for m in self.margin_stack if m] - if not margin and fstack: - margin = fstack[-1] - self.writer.new_margin(margin, len(fstack)) - - def pop_margin(self): - if self.margin_stack: - del self.margin_stack[-1] - fstack = [m for m in self.margin_stack if m] - if fstack: - margin = fstack[-1] - else: - margin = None - self.writer.new_margin(margin, len(fstack)) - - def set_spacing(self, spacing): - self.spacing = spacing - self.writer.new_spacing(spacing) - - def push_style(self, *styles): - if self.softspace: - self.hard_break = self.para_end = self.softspace = 0 - self.nospace = 1 - self.writer.send_flowing_data(' ') - for style in styles: - self.style_stack.append(style) - self.writer.new_styles(tuple(self.style_stack)) - - def pop_style(self, n=1): - del self.style_stack[-n:] - self.writer.new_styles(tuple(self.style_stack)) - - def assert_line_data(self, flag=1): - self.nospace = self.hard_break = not flag - self.para_end = self.parskip = self.have_label = 0 - - -class NullWriter: - """Minimal writer interface to use in testing & inheritance. - - A writer which only provides the interface definition; no actions are - taken on any methods. This should be the base class for all writers - which do not need to inherit any implementation methods. - - """ - def __init__(self): pass - def flush(self): pass - def new_alignment(self, align): pass - def new_font(self, font): pass - def new_margin(self, margin, level): pass - def new_spacing(self, spacing): pass - def new_styles(self, styles): pass - def send_paragraph(self, blankline): pass - def send_line_break(self): pass - def send_hor_rule(self, *args, **kw): pass - def send_label_data(self, data): pass - def send_flowing_data(self, data): pass - def send_literal_data(self, data): pass - - -class AbstractWriter(NullWriter): - """A writer which can be used in debugging formatters, but not much else. - - Each method simply announces itself by printing its name and - arguments on standard output. - - """ - - def new_alignment(self, align): - print("new_alignment(%r)" % (align,)) - - def new_font(self, font): - print("new_font(%r)" % (font,)) - - def new_margin(self, margin, level): - print("new_margin(%r, %d)" % (margin, level)) - - def new_spacing(self, spacing): - print("new_spacing(%r)" % (spacing,)) - - def new_styles(self, styles): - print("new_styles(%r)" % (styles,)) - - def send_paragraph(self, blankline): - print("send_paragraph(%r)" % (blankline,)) - - def send_line_break(self): - print("send_line_break()") - - def send_hor_rule(self, *args, **kw): - print("send_hor_rule()") - - def send_label_data(self, data): - print("send_label_data(%r)" % (data,)) - - def send_flowing_data(self, data): - print("send_flowing_data(%r)" % (data,)) - - def send_literal_data(self, data): - print("send_literal_data(%r)" % (data,)) - - -class DumbWriter(NullWriter): - """Simple writer class which writes output on the file object passed in - as the file parameter or, if file is omitted, on standard output. The - output is simply word-wrapped to the number of columns specified by - the maxcol parameter. This class is suitable for reflowing a sequence - of paragraphs. - - """ - - def __init__(self, file=None, maxcol=72): - self.file = file or sys.stdout - self.maxcol = maxcol - NullWriter.__init__(self) - self.reset() - - def reset(self): - self.col = 0 - self.atbreak = 0 - - def send_paragraph(self, blankline): - self.file.write('\n'*blankline) - self.col = 0 - self.atbreak = 0 - - def send_line_break(self): - self.file.write('\n') - self.col = 0 - self.atbreak = 0 - - def send_hor_rule(self, *args, **kw): - self.file.write('\n') - self.file.write('-'*self.maxcol) - self.file.write('\n') - self.col = 0 - self.atbreak = 0 - - def send_literal_data(self, data): - self.file.write(data) - i = data.rfind('\n') - if i >= 0: - self.col = 0 - data = data[i+1:] - data = data.expandtabs() - self.col = self.col + len(data) - self.atbreak = 0 - - def send_flowing_data(self, data): - if not data: return - atbreak = self.atbreak or data[0].isspace() - col = self.col - maxcol = self.maxcol - write = self.file.write - for word in data.split(): - if atbreak: - if col + len(word) >= maxcol: - write('\n') - col = 0 - else: - write(' ') - col = col + 1 - write(word) - col = col + len(word) - atbreak = 1 - self.col = col - self.atbreak = data[-1].isspace() - - -def test(file = None): - w = DumbWriter() - f = AbstractFormatter(w) - if file is not None: - fp = open(file) - elif sys.argv[1:]: - fp = open(sys.argv[1]) - else: - fp = sys.stdin - try: - for line in fp: - if line == '\n': - f.end_paragraph(1) - else: - f.add_flowing_data(line) - finally: - if fp is not sys.stdin: - fp.close() - f.end_paragraph(0) - - -if __name__ == '__main__': - test() diff --git a/Lib/http/client.py b/Lib/http/client.py index 15abcfeada..4eca93ef26 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -861,7 +861,7 @@ class HTTPConnection: the endpoint passed to `set_tunnel`. This done by sending an HTTP CONNECT request to the proxy server when the connection is established. - This method must be called before the HTML connection has been + This method must be called before the HTTP connection has been established. The headers argument should be a mapping of extra HTTP headers to send @@ -1407,6 +1407,9 @@ else: self.cert_file = cert_file if context is None: context = ssl._create_default_https_context() + # send ALPN extension to indicate HTTP/1.1 protocol + if self._http_vsn == 11: + context.set_alpn_protocols(['http/1.1']) # enable PHA for TLS 1.3 connections if available if context.post_handshake_auth is not None: context.post_handshake_auth = True diff --git a/Lib/http/server.py b/Lib/http/server.py index fa204fbc15..94f730ed34 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -414,7 +414,7 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): method = getattr(self, mname) method() self.wfile.flush() #actually send the response if not already done. - except socket.timeout as e: + except TimeoutError as e: #a read or a write timed out. Discard this connection self.log_error("Request timed out: %r", e) self.close_connection = True @@ -1092,8 +1092,7 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): env['PATH_INFO'] = uqrest env['PATH_TRANSLATED'] = self.translate_path(uqrest) env['SCRIPT_NAME'] = scriptname - if query: - env['QUERY_STRING'] = query + env['QUERY_STRING'] = query env['REMOTE_ADDR'] = self.client_address[0] authorization = self.headers.get("authorization") if authorization: @@ -1123,12 +1122,7 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): referer = self.headers.get('referer') if referer: env['HTTP_REFERER'] = referer - accept = [] - for line in self.headers.getallmatchingheaders('accept'): - if line[:1] in "\t\n\r ": - accept.append(line.strip()) - else: - accept = accept + line[7:].split(',') + accept = self.headers.get_all('accept', ()) env['HTTP_ACCEPT'] = ','.join(accept) ua = self.headers.get('user-agent') if ua: diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index 3ece623b3a..7167314ca7 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -3,6 +3,15 @@ Released on 2021-10-04? ====================================== +bpo-42508: Keep IDLE running on macOS. Remove obsolete workaround +that prevented running files with shortcuts when using new universal2 +installers built on macOS 11. + +bpo-42426: Fix reporting offset of the RE error in searchengine. + +bpo-42416: Get docstrings for IDLE calltips more often +by using inspect.getdoc. + bpo-33987: Mostly finish using ttk widgets, mainly for editor, settings, and searches. Some patches by Mark Roseman. diff --git a/Lib/idlelib/calltip.py b/Lib/idlelib/calltip.py index 549e224015..40bc5a0ad7 100644 --- a/Lib/idlelib/calltip.py +++ b/Lib/idlelib/calltip.py @@ -165,6 +165,7 @@ def get_argspec(ob): ob_call = ob.__call__ except BaseException: # Buggy user object could raise anything. return '' # No popup for non-callables. + # For Get_argspecTest.test_buggy_getattr_class, CallA() & CallB(). fob = ob_call if isinstance(ob_call, types.MethodType) else ob # Initialize argspec and wrap it to get lines. @@ -185,10 +186,7 @@ def get_argspec(ob): if len(argspec) > _MAX_COLS else [argspec] if argspec else []) # Augment lines from docstring, if any, and join to get argspec. - if isinstance(ob_call, types.MethodType): - doc = ob_call.__doc__ - else: - doc = getattr(ob, "__doc__", "") + doc = inspect.getdoc(ob) if doc: for line in doc.split('\n', _MAX_LINES)[:_MAX_LINES]: line = line.strip() diff --git a/Lib/idlelib/codecontext.py b/Lib/idlelib/codecontext.py index 989b30e599..eb19773f56 100644 --- a/Lib/idlelib/codecontext.py +++ b/Lib/idlelib/codecontext.py @@ -7,11 +7,14 @@ the lines which contain the block opening keywords, e.g. 'if', for the enclosing block. The number of hint lines is determined by the maxlines variable in the codecontext section of config-extensions.def. Lines which do not open blocks are not shown in the context hints pane. + +For EditorWindows, <<toggle-code-context>> is bound to CodeContext(self). +toggle_code_context_event. """ import re from sys import maxsize as INFINITY -import tkinter +from tkinter import Frame, Text, TclError from tkinter.constants import NSEW, SUNKEN from idlelib.config import idleConf @@ -83,7 +86,7 @@ class CodeContext: if self.t1 is not None: try: self.text.after_cancel(self.t1) - except tkinter.TclError: # pragma: no cover + except TclError: # pragma: no cover pass self.t1 = None @@ -111,7 +114,7 @@ class CodeContext: padx += widget.tk.getint(info['padx']) padx += widget.tk.getint(widget.cget('padx')) border += widget.tk.getint(widget.cget('border')) - context = self.context = tkinter.Text( + context = self.context = Text( self.editwin.text_frame, height=1, width=1, # Don't request more than we get. @@ -127,7 +130,7 @@ class CodeContext: line_number_colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'linenumber') - self.cell00 = tkinter.Frame(self.editwin.text_frame, + self.cell00 = Frame(self.editwin.text_frame, bg=line_number_colors['background']) self.cell00.grid(row=0, column=0, sticky=NSEW) menu_status = 'Hide' @@ -221,7 +224,7 @@ class CodeContext: """ try: self.context.index("sel.first") - except tkinter.TclError: + except TclError: lines = len(self.info) if lines == 1: # No context lines are showing. newtop = 1 diff --git a/Lib/idlelib/idle_test/test_calltip.py b/Lib/idlelib/idle_test/test_calltip.py index 489b6899ba..a76829f365 100644 --- a/Lib/idlelib/idle_test/test_calltip.py +++ b/Lib/idlelib/idle_test/test_calltip.py @@ -99,7 +99,12 @@ non-overlapping occurrences o...''') (width=70, initial_indent='', subsequent_indent='', expand_tabs=True, replace_whitespace=True, fix_sentence_endings=False, break_long_words=True, drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None, - placeholder=' [...]')''') + placeholder=' [...]') +Object for wrapping/filling text. The public interface consists of +the wrap() and fill() methods; the other methods are just there for +subclasses to override in order to tweak the default behaviour. +If you want to completely replace the main wrapping algorithm, +you\'ll probably have to override _wrap_chunks().''') def test_properly_formated(self): @@ -241,7 +246,7 @@ bytes() -> empty bytes object''') __class__ = property({}.__getitem__, {}.__setitem__) class Object(metaclass=Type): __slots__ = '__class__' - for meth, mtip in ((Type, default_tip), (Object, default_tip), + for meth, mtip in ((Type, get_spec(type)), (Object, default_tip), (Object(), '')): with self.subTest(meth=meth, mtip=mtip): self.assertEqual(get_spec(meth), mtip) diff --git a/Lib/idlelib/idle_test/test_searchengine.py b/Lib/idlelib/idle_test/test_searchengine.py index 3d26d62a95..f8401ce938 100644 --- a/Lib/idlelib/idle_test/test_searchengine.py +++ b/Lib/idlelib/idle_test/test_searchengine.py @@ -175,11 +175,13 @@ class SearchEngineTest(unittest.TestCase): engine.setpat('') Equal(engine.getprog(), None) + Equal(Mbox.showerror.message, + 'Error: Empty regular expression') engine.setpat('+') engine.revar.set(1) Equal(engine.getprog(), None) - self.assertEqual(Mbox.showerror.message, - 'Error: nothing to repeat at position 0\nPattern: +') + Equal(Mbox.showerror.message, + 'Error: nothing to repeat\nPattern: +\nOffset: 0') def test_report_error(self): showerror = Mbox.showerror diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index b69916dbe8..abe8a85952 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -463,7 +463,7 @@ class ModifiedInterpreter(InteractiveInterpreter): self.rpcclt.listening_sock.settimeout(10) try: self.rpcclt.accept() - except socket.timeout: + except TimeoutError: self.display_no_subprocess_error() return None self.rpcclt.register("console", self.tkconsole) @@ -498,7 +498,7 @@ class ModifiedInterpreter(InteractiveInterpreter): self.spawn_subprocess() try: self.rpcclt.accept() - except socket.timeout: + except TimeoutError: self.display_no_subprocess_error() return None self.transfer_path(with_cwd=with_cwd) @@ -757,7 +757,7 @@ class ModifiedInterpreter(InteractiveInterpreter): def runcode(self, code): "Override base class method" if self.tkconsole.executing: - self.interp.restart_subprocess() + self.restart_subprocess() self.checklinecache() debugger = self.debugger try: @@ -1061,8 +1061,10 @@ class PyShell(OutputWindow): (sys.version, sys.platform, self.COPYRIGHT, nosub)) self.text.focus_force() self.showprompt() + # User code should use separate default Tk root window import tkinter - tkinter._default_root = None # 03Jan04 KBK What's this? + tkinter._support_default_root = True + tkinter._default_root = None return True def stop_readline(self): diff --git a/Lib/idlelib/runscript.py b/Lib/idlelib/runscript.py index a54108794a..028b0dbd21 100644 --- a/Lib/idlelib/runscript.py +++ b/Lib/idlelib/runscript.py @@ -11,6 +11,7 @@ TODO: Specify command line arguments in a dialog box. """ import os import tabnanny +import time import tokenize import tkinter.messagebox as tkMessageBox @@ -42,9 +43,7 @@ class ScriptBinding: self.root = self.editwin.root # cli_args is list of strings that extends sys.argv self.cli_args = [] - - if macosx.isCocoaTk(): - self.editwin.text_frame.bind('<<run-module-event-2>>', self._run_module_event) + self.perf = 0.0 # Workaround for macOS 11 Uni2; see bpo-42508. def check_module_event(self, event): if isinstance(self.editwin, outwin.OutputWindow): @@ -107,24 +106,10 @@ class ScriptBinding: finally: shell.set_warning_stream(saved_stream) - def run_module_event(self, event): - if macosx.isCocoaTk(): - # Tk-Cocoa in MacOSX is broken until at least - # Tk 8.5.9, and without this rather - # crude workaround IDLE would hang when a user - # tries to run a module using the keyboard shortcut - # (the menu item works fine). - self.editwin.text_frame.after(200, - lambda: self.editwin.text_frame.event_generate( - '<<run-module-event-2>>')) - return 'break' - else: - return self._run_module_event(event) - def run_custom_event(self, event): - return self._run_module_event(event, customize=True) + return self.run_module_event(event, customize=True) - def _run_module_event(self, event, *, customize=False): + def run_module_event(self, event, *, customize=False): """Run the module after setting up the environment. First check the syntax. Next get customization. If OK, make @@ -133,6 +118,8 @@ class ScriptBinding: module being executed and also add that directory to its sys.path if not already included. """ + if macosx.isCocoaTk() and (time.perf_counter() - self.perf < .05): + return 'break' if isinstance(self.editwin, outwin.OutputWindow): self.editwin.text.bell() return 'break' @@ -218,6 +205,7 @@ class ScriptBinding: # XXX This should really be a function of EditorWindow... tkMessageBox.showerror(title, message, parent=self.editwin.text) self.editwin.text.focus_set() + self.perf = time.perf_counter() if __name__ == "__main__": diff --git a/Lib/idlelib/searchengine.py b/Lib/idlelib/searchengine.py index 911e7d4691..a50038e282 100644 --- a/Lib/idlelib/searchengine.py +++ b/Lib/idlelib/searchengine.py @@ -84,20 +84,17 @@ class SearchEngine: flags = flags | re.IGNORECASE try: prog = re.compile(pat, flags) - except re.error as what: - args = what.args - msg = args[0] - col = args[1] if len(args) >= 2 else -1 - self.report_error(pat, msg, col) + except re.error as e: + self.report_error(pat, e.msg, e.pos) return None return prog - def report_error(self, pat, msg, col=-1): + def report_error(self, pat, msg, col=None): # Derived class could override this with something fancier msg = "Error: " + str(msg) if pat: msg = msg + "\nPattern: " + str(pat) - if col >= 0: + if col is not None: msg = msg + "\nOffset: " + str(col) tkMessageBox.showerror("Regular expression error", msg, master=self.root) diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py index bea37d7662..03ff71489a 100644 --- a/Lib/importlib/__init__.py +++ b/Lib/importlib/__init__.py @@ -34,7 +34,7 @@ try: import _frozen_importlib_external as _bootstrap_external except ImportError: from . import _bootstrap_external - _bootstrap_external._setup(_bootstrap) + _bootstrap_external._set_bootstrap_module(_bootstrap) _bootstrap._bootstrap_external = _bootstrap_external else: _bootstrap_external.__name__ = 'importlib._bootstrap_external' diff --git a/Lib/importlib/_abc.py b/Lib/importlib/_abc.py index fb5ec727ce..7591946a4e 100644 --- a/Lib/importlib/_abc.py +++ b/Lib/importlib/_abc.py @@ -35,6 +35,7 @@ class Loader(metaclass=abc.ABCMeta): """ if not hasattr(self, 'exec_module'): raise ImportError + # Warning implemented in _load_module_shim(). return _bootstrap._load_module_shim(self, fullname) def module_repr(self, module): diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index e00b27ece2..e4f893c38c 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -20,10 +20,23 @@ work. One should use importlib as the public-facing version of this module. # reference any injected objects! This includes not only global code but also # anything specified at the class level. +def _object_name(obj): + try: + return obj.__qualname__ + except AttributeError: + return type(obj).__qualname__ + # Bootstrap-related code ###################################################### +# Modules injected manually by _setup() +_thread = None +_warnings = None +_weakref = None + +# Import done by _install_external_importers() _bootstrap_external = None + def _wrap(new, old): """Simple substitute for functools.update_wrapper.""" for replace in ['__module__', '__name__', '__qualname__', '__doc__']: @@ -265,6 +278,9 @@ def _load_module_shim(self, fullname): This method is deprecated. Use loader.exec_module instead. """ + msg = ("the load_module() method is deprecated and slated for removal in " + "Python 3.12; use exec_module() instead") + _warnings.warn(msg, DeprecationWarning) spec = spec_from_loader(fullname, self) if fullname in sys.modules: module = sys.modules[fullname] @@ -605,9 +621,9 @@ def _exec(spec, module): else: _init_module_attrs(spec, module, override=True) if not hasattr(spec.loader, 'exec_module'): - # (issue19713) Once BuiltinImporter and ExtensionFileLoader - # have exec_module() implemented, we can add a deprecation - # warning here. + msg = (f"{_object_name(spec.loader)}.exec_module() not found; " + "falling back to load_module()") + _warnings.warn(msg, ImportWarning) spec.loader.load_module(name) else: spec.loader.exec_module(module) @@ -620,9 +636,8 @@ def _exec(spec, module): def _load_backward_compatible(spec): - # (issue19713) Once BuiltinImporter and ExtensionFileLoader - # have exec_module() implemented, we can add a deprecation - # warning here. + # It is assumed that all callers have been warned about using load_module() + # appropriately before calling this function. try: spec.loader.load_module(spec.name) except: @@ -661,6 +676,9 @@ def _load_unlocked(spec): if spec.loader is not None: # Not a namespace package. if not hasattr(spec.loader, 'exec_module'): + msg = (f"{_object_name(spec.loader)}.exec_module() not found; " + "falling back to load_module()") + _warnings.warn(msg, ImportWarning) return _load_backward_compatible(spec) module = module_from_spec(spec) @@ -754,16 +772,16 @@ class BuiltinImporter: spec = cls.find_spec(fullname, path) return spec.loader if spec is not None else None - @classmethod - def create_module(self, spec): + @staticmethod + def create_module(spec): """Create a built-in module""" if spec.name not in sys.builtin_module_names: raise ImportError('{!r} is not a built-in module'.format(spec.name), name=spec.name) return _call_with_frames_removed(_imp.create_builtin, spec) - @classmethod - def exec_module(self, module): + @staticmethod + def exec_module(module): """Exec a built-in module""" _call_with_frames_removed(_imp.exec_builtin, module) @@ -824,8 +842,8 @@ class FrozenImporter: """ return cls if _imp.is_frozen(fullname) else None - @classmethod - def create_module(cls, spec): + @staticmethod + def create_module(spec): """Use default semantics for module creation.""" @staticmethod @@ -844,6 +862,7 @@ class FrozenImporter: This method is deprecated. Use exec_module() instead. """ + # Warning about deprecation implemented in _load_module_shim(). return _load_module_shim(cls, fullname) @classmethod diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index b08ad032ab..354650011e 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -19,6 +19,36 @@ work. One should use importlib as the public-facing version of this module. # reference any injected objects! This includes not only global code but also # anything specified at the class level. +# Module injected manually by _set_bootstrap_module() +_bootstrap = None + +# Import builtin modules +import _imp +import _io +import sys +import _warnings +import marshal + + +_MS_WINDOWS = (sys.platform == 'win32') +if _MS_WINDOWS: + import nt as _os + import winreg +else: + import posix as _os + + +if _MS_WINDOWS: + path_separators = ['\\', '/'] +else: + path_separators = ['/'] +# Assumption made in _path_join() +assert all(len(sep) == 1 for sep in path_separators) +path_sep = path_separators[0] +path_separators = ''.join(path_separators) +_pathseps_with_colon = {f':{s}' for s in path_separators} + + # Bootstrap-related code ###################################################### _CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win', _CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin' @@ -42,6 +72,8 @@ def _make_relax_case(): return False return _relax_case +_relax_case = _make_relax_case() + def _pack_uint32(x): """Convert a 32-bit integer to little-endian.""" @@ -278,6 +310,9 @@ _code_type = type(_write_atomic.__code__) # Python 3.9a2 3424 (simplify bytecodes for *value unpacking) # Python 3.9a2 3425 (simplify bytecodes for **value unpacking) # Python 3.10a1 3430 (Make 'annotations' future by default) +# Python 3.10a1 3431 (New line number table format -- PEP 626) +# Python 3.10a2 3432 (Function annotation for MAKE_FUNCTION is changed from dict to tuple bpo-42202) +# Python 3.10a2 3433 (RERAISE restores f_lasti if oparg != 0) # # MAGIC must change whenever the bytecode emitted by the compiler may no @@ -287,13 +322,17 @@ _code_type = type(_write_atomic.__code__) # Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array # in PC/launcher.c must also be updated. -MAGIC_NUMBER = (3430).to_bytes(2, 'little') + b'\r\n' +MAGIC_NUMBER = (3433).to_bytes(2, 'little') + b'\r\n' _RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c _PYCACHE = '__pycache__' _OPT = 'opt-' -SOURCE_SUFFIXES = ['.py'] # _setup() adds .pyw as needed. +SOURCE_SUFFIXES = ['.py'] +if _MS_WINDOWS: + SOURCE_SUFFIXES.append('.pyw') + +EXTENSION_SUFFIXES = _imp.extension_suffixes() BYTECODE_SUFFIXES = ['.pyc'] # Deprecated. @@ -468,15 +507,18 @@ def _check_name(method): raise ImportError('loader for %s cannot handle %s' % (self.name, name), name=name) return method(self, name, *args, **kwargs) - try: + + # FIXME: @_check_name is used to define class methods before the + # _bootstrap module is set by _set_bootstrap_module(). + if _bootstrap is not None: _wrap = _bootstrap._wrap - except NameError: - # XXX yuck + else: def _wrap(new, old): for replace in ['__module__', '__name__', '__qualname__', '__doc__']: if hasattr(old, replace): setattr(new, replace, getattr(old, replace)) new.__dict__.update(old.__dict__) + _wrap(_check_name_wrapper, method) return _check_name_wrapper @@ -712,10 +754,10 @@ class WindowsRegistryFinder: REGISTRY_KEY_DEBUG = ( 'Software\\Python\\PythonCore\\{sys_version}' '\\Modules\\{fullname}\\Debug') - DEBUG_BUILD = False # Changed in _setup() + DEBUG_BUILD = (_MS_WINDOWS and '_d.pyd' in EXTENSION_SUFFIXES) - @classmethod - def _open_registry(cls, key): + @staticmethod + def _open_registry(key): try: return winreg.OpenKey(winreg.HKEY_CURRENT_USER, key) except OSError: @@ -791,7 +833,8 @@ class _LoaderBasics: _bootstrap._call_with_frames_removed(exec, code, module.__dict__) def load_module(self, fullname): - """This module is deprecated.""" + """This method is deprecated.""" + # Warning implemented in _load_module_shim(). return _bootstrap._load_module_shim(self, fullname) @@ -966,7 +1009,7 @@ class FileLoader: """ # The only reason for this method is for the name check. # Issue #14857: Avoid the zero-argument form of super so the implementation - # of that form can be updated without breaking the frozen module + # of that form can be updated without breaking the frozen module. return super(FileLoader, self).load_module(fullname) @_check_name @@ -1059,10 +1102,6 @@ class SourcelessFileLoader(FileLoader, _LoaderBasics): return None -# Filled in by _setup(). -EXTENSION_SUFFIXES = [] - - class ExtensionFileLoader(FileLoader, _LoaderBasics): """Loader for extension modules. @@ -1183,8 +1222,8 @@ class _NamespaceLoader: def __init__(self, name, path, path_finder): self._path = _NamespacePath(name, path, path_finder) - @classmethod - def module_repr(cls, module): + @staticmethod + def module_repr(module): """Return repr for the module. The method is deprecated. The import machinery does the job itself. @@ -1216,6 +1255,7 @@ class _NamespaceLoader: # The import system never calls this method. _bootstrap._verbose_message('namespace module loaded with path {!r}', self._path) + # Warning implemented in _load_module_shim(). return _bootstrap._load_module_shim(self, fullname) @@ -1225,8 +1265,8 @@ class PathFinder: """Meta path finder for sys.path and package __path__ attributes.""" - @classmethod - def invalidate_caches(cls): + @staticmethod + def invalidate_caches(): """Call the invalidate_caches() method on all path entry finders stored in sys.path_importer_caches (where implemented).""" for name, finder in list(sys.path_importer_cache.items()): @@ -1235,8 +1275,8 @@ class PathFinder: elif hasattr(finder, 'invalidate_caches'): finder.invalidate_caches() - @classmethod - def _path_hooks(cls, path): + @staticmethod + def _path_hooks(path): """Search sys.path_hooks for a finder for 'path'.""" if sys.path_hooks is not None and not sys.path_hooks: _warnings.warn('sys.path_hooks is empty', ImportWarning) @@ -1354,8 +1394,8 @@ class PathFinder: return None return spec.loader - @classmethod - def find_distributions(cls, *args, **kwargs): + @staticmethod + def find_distributions(*args, **kwargs): """ Find distributions. @@ -1551,66 +1591,14 @@ def _get_supported_file_loaders(): return [extensions, source, bytecode] -def _setup(_bootstrap_module): - """Setup the path-based importers for importlib by importing needed - built-in modules and injecting them into the global namespace. - - Other components are extracted from the core bootstrap module. - - """ - global sys, _imp, _bootstrap +def _set_bootstrap_module(_bootstrap_module): + global _bootstrap _bootstrap = _bootstrap_module - sys = _bootstrap.sys - _imp = _bootstrap._imp - - self_module = sys.modules[__name__] - - # Directly load the os module (needed during bootstrap). - os_details = ('posix', ['/']), ('nt', ['\\', '/']) - for builtin_os, path_separators in os_details: - # Assumption made in _path_join() - assert all(len(sep) == 1 for sep in path_separators) - path_sep = path_separators[0] - if builtin_os in sys.modules: - os_module = sys.modules[builtin_os] - break - else: - try: - os_module = _bootstrap._builtin_from_name(builtin_os) - break - except ImportError: - continue - else: - raise ImportError('importlib requires posix or nt') - - setattr(self_module, '_os', os_module) - setattr(self_module, 'path_sep', path_sep) - setattr(self_module, 'path_separators', ''.join(path_separators)) - setattr(self_module, '_pathseps_with_colon', {f':{s}' for s in path_separators}) - - # Directly load built-in modules needed during bootstrap. - builtin_names = ['_io', '_warnings', 'marshal'] - if builtin_os == 'nt': - builtin_names.append('winreg') - for builtin_name in builtin_names: - if builtin_name not in sys.modules: - builtin_module = _bootstrap._builtin_from_name(builtin_name) - else: - builtin_module = sys.modules[builtin_name] - setattr(self_module, builtin_name, builtin_module) - - # Constants - setattr(self_module, '_relax_case', _make_relax_case()) - EXTENSION_SUFFIXES.extend(_imp.extension_suffixes()) - if builtin_os == 'nt': - SOURCE_SUFFIXES.append('.pyw') - if '_d.pyd' in EXTENSION_SUFFIXES: - WindowsRegistryFinder.DEBUG_BUILD = True def _install(_bootstrap_module): """Install the path-based import components.""" - _setup(_bootstrap_module) + _set_bootstrap_module(_bootstrap_module) supported_loaders = _get_supported_file_loaders() sys.path_hooks.extend([FileFinder.path_hook(*supported_loaders)]) sys.meta_path.append(PathFinder) diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py index 97d5afa300..55e70889f2 100644 --- a/Lib/importlib/abc.py +++ b/Lib/importlib/abc.py @@ -1,5 +1,4 @@ """Abstract base classes related to import.""" -from . import _bootstrap from . import _bootstrap_external from . import machinery try: diff --git a/Lib/importlib/machinery.py b/Lib/importlib/machinery.py index 1b2b5c9b4f..9a7757fb6e 100644 --- a/Lib/importlib/machinery.py +++ b/Lib/importlib/machinery.py @@ -1,7 +1,5 @@ """The machinery of importlib: finders, loaders, hooks, etc.""" -import _imp - from ._bootstrap import ModuleSpec from ._bootstrap import BuiltinImporter from ._bootstrap import FrozenImporter diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 1e44843a68..98a0fa54df 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -232,7 +232,6 @@ class _LazyModule(types.ModuleType): # Figure out exactly what attributes were mutated between the creation # of the module and now. attrs_then = self.__spec__.loader_state['__dict__'] - original_type = self.__spec__.loader_state['__class__'] attrs_now = self.__dict__ attrs_updated = {} for key, value in attrs_now.items(): diff --git a/Lib/inspect.py b/Lib/inspect.py index ac127cbd72..70c5ef7bb6 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -707,10 +707,13 @@ def getsourcefile(object): if os.path.exists(filename): return filename # only return a non-existent filename if the module has a PEP 302 loader - if getattr(getmodule(object, filename), '__loader__', None) is not None: + module = getmodule(object, filename) + if getattr(module, '__loader__', None) is not None: + return filename + elif getattr(getattr(module, "__spec__", None), "loader", None) is not None: return filename # or it is in the linecache - if filename in linecache.cache: + elif filename in linecache.cache: return filename def getabsfile(object, _filename=None): @@ -865,7 +868,12 @@ def findsource(object): lnum = object.co_firstlineno - 1 pat = re.compile(r'^(\s*def\s)|(\s*async\s+def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)') while lnum > 0: - if pat.match(lines[lnum]): break + try: + line = lines[lnum] + except IndexError: + raise OSError('lineno is out of bounds') + if pat.match(line): + break lnum = lnum - 1 return lines, lnum raise OSError('could not find code object') @@ -927,6 +935,7 @@ class BlockFinder: self.indecorator = False self.decoratorhasargs = False self.last = 1 + self.body_col0 = None def tokeneater(self, type, token, srowcol, erowcol, line): if not self.started and not self.indecorator: @@ -958,6 +967,8 @@ class BlockFinder: elif self.passline: pass elif type == tokenize.INDENT: + if self.body_col0 is None and self.started: + self.body_col0 = erowcol[1] self.indent = self.indent + 1 self.passline = True elif type == tokenize.DEDENT: @@ -967,6 +978,10 @@ class BlockFinder: # not e.g. for "if: else:" or "try: finally:" blocks) if self.indent <= 0: raise EndOfBlock + elif type == tokenize.COMMENT: + if self.body_col0 is not None and srowcol[1] >= self.body_col0: + # Include comments if indented at least as much as the block + self.last = srowcol[0] elif self.indent == 0 and type not in (tokenize.COMMENT, tokenize.NL): # any other token on the same indentation level end the previous # block as well, except the pseudo-tokens COMMENT and NL. @@ -2122,9 +2137,9 @@ def _signature_fromstr(cls, obj, s, skip_bound_arg=True): return cls(parameters, return_annotation=cls.empty) -def _get_type_hints(func): +def _get_type_hints(func, **kwargs): try: - return typing.get_type_hints(func) + return typing.get_type_hints(func, **kwargs) except Exception: # First, try to use the get_type_hints to resolve # annotations. But for keeping the behavior intact @@ -2149,7 +2164,8 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True): return _signature_fromstr(cls, func, s, skip_bound_arg) -def _signature_from_function(cls, func, skip_bound_arg=True): +def _signature_from_function(cls, func, skip_bound_arg=True, + globalns=None, localns=None): """Private helper: constructs Signature for the given python function.""" is_duck_function = False @@ -2175,7 +2191,7 @@ def _signature_from_function(cls, func, skip_bound_arg=True): positional = arg_names[:pos_count] keyword_only_count = func_code.co_kwonlyargcount keyword_only = arg_names[pos_count:pos_count + keyword_only_count] - annotations = _get_type_hints(func) + annotations = _get_type_hints(func, globalns=globalns, localns=localns) defaults = func.__defaults__ kwdefaults = func.__kwdefaults__ @@ -2247,23 +2263,28 @@ def _signature_from_function(cls, func, skip_bound_arg=True): def _signature_from_callable(obj, *, follow_wrapper_chains=True, skip_bound_arg=True, + globalns=None, + localns=None, sigcls): """Private helper function to get signature for arbitrary callable objects. """ + _get_signature_of = functools.partial(_signature_from_callable, + follow_wrapper_chains=follow_wrapper_chains, + skip_bound_arg=skip_bound_arg, + globalns=globalns, + localns=localns, + sigcls=sigcls) + if not callable(obj): raise TypeError('{!r} is not a callable object'.format(obj)) if isinstance(obj, types.MethodType): # In this case we skip the first parameter of the underlying # function (usually `self` or `cls`). - sig = _signature_from_callable( - obj.__func__, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) + sig = _get_signature_of(obj.__func__) if skip_bound_arg: return _signature_bound_method(sig) @@ -2277,11 +2298,7 @@ def _signature_from_callable(obj, *, # If the unwrapped object is a *method*, we might want to # skip its first parameter (self). # See test_signature_wrapped_bound_method for details. - return _signature_from_callable( - obj, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) + return _get_signature_of(obj) try: sig = obj.__signature__ @@ -2308,11 +2325,7 @@ def _signature_from_callable(obj, *, # (usually `self`, or `cls`) will not be passed # automatically (as for boundmethods) - wrapped_sig = _signature_from_callable( - partialmethod.func, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) + wrapped_sig = _get_signature_of(partialmethod.func) sig = _signature_get_partial(wrapped_sig, partialmethod, (None,)) first_wrapped_param = tuple(wrapped_sig.parameters.values())[0] @@ -2331,18 +2344,15 @@ def _signature_from_callable(obj, *, # If it's a pure Python function, or an object that is duck type # of a Python function (Cython functions, for instance), then: return _signature_from_function(sigcls, obj, - skip_bound_arg=skip_bound_arg) + skip_bound_arg=skip_bound_arg, + globalns=globalns, localns=localns) if _signature_is_builtin(obj): return _signature_from_builtin(sigcls, obj, skip_bound_arg=skip_bound_arg) if isinstance(obj, functools.partial): - wrapped_sig = _signature_from_callable( - obj.func, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) + wrapped_sig = _get_signature_of(obj.func) return _signature_get_partial(wrapped_sig, obj) sig = None @@ -2353,29 +2363,17 @@ def _signature_from_callable(obj, *, # in its metaclass call = _signature_get_user_defined_method(type(obj), '__call__') if call is not None: - sig = _signature_from_callable( - call, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) + sig = _get_signature_of(call) else: # Now we check if the 'obj' class has a '__new__' method new = _signature_get_user_defined_method(obj, '__new__') if new is not None: - sig = _signature_from_callable( - new, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) + sig = _get_signature_of(new) else: # Finally, we should have at least __init__ implemented init = _signature_get_user_defined_method(obj, '__init__') if init is not None: - sig = _signature_from_callable( - init, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) + sig = _get_signature_of(init) if sig is None: # At this point we know, that `obj` is a class, with no user- @@ -2421,11 +2419,7 @@ def _signature_from_callable(obj, *, call = _signature_get_user_defined_method(type(obj), '__call__') if call is not None: try: - sig = _signature_from_callable( - call, - follow_wrapper_chains=follow_wrapper_chains, - skip_bound_arg=skip_bound_arg, - sigcls=sigcls) + sig = _get_signature_of(call) except ValueError as ex: msg = 'no signature found for {!r}'.format(obj) raise ValueError(msg) from ex @@ -2877,10 +2871,12 @@ class Signature: return _signature_from_builtin(cls, func) @classmethod - def from_callable(cls, obj, *, follow_wrapped=True): + def from_callable(cls, obj, *, + follow_wrapped=True, globalns=None, localns=None): """Constructs Signature for the given callable object.""" return _signature_from_callable(obj, sigcls=cls, - follow_wrapper_chains=follow_wrapped) + follow_wrapper_chains=follow_wrapped, + globalns=globalns, localns=localns) @property def parameters(self): @@ -3128,9 +3124,10 @@ class Signature: return rendered -def signature(obj, *, follow_wrapped=True): +def signature(obj, *, follow_wrapped=True, globalns=None, localns=None): """Get a signature object for the passed callable.""" - return Signature.from_callable(obj, follow_wrapped=follow_wrapped) + return Signature.from_callable(obj, follow_wrapped=follow_wrapped, + globalns=globalns, localns=localns) def _main(): diff --git a/Lib/lib2to3/Grammar.txt b/Lib/lib2to3/Grammar.txt index e007dc188a..fa7b15061d 100644 --- a/Lib/lib2to3/Grammar.txt +++ b/Lib/lib2to3/Grammar.txt @@ -18,15 +18,55 @@ decorated: decorators (classdef | funcdef | async_funcdef) async_funcdef: ASYNC funcdef funcdef: 'def' NAME parameters ['->' test] ':' suite parameters: '(' [typedargslist] ')' -typedargslist: ((tfpdef ['=' test] ',')* - ('*' [tname] (',' tname ['=' test])* [',' ['**' tname [',']]] | '**' tname [',']) - | tfpdef ['=' test] (',' tfpdef ['=' test])* [',']) + +# The following definition for typedarglist is equivalent to this set of rules: +# +# arguments = argument (',' argument)* +# argument = tfpdef ['=' test] +# kwargs = '**' tname [','] +# args = '*' [tname] +# kwonly_kwargs = (',' argument)* [',' [kwargs]] +# args_kwonly_kwargs = args kwonly_kwargs | kwargs +# poskeyword_args_kwonly_kwargs = arguments [',' [args_kwonly_kwargs]] +# typedargslist_no_posonly = poskeyword_args_kwonly_kwargs | args_kwonly_kwargs +# typedarglist = arguments ',' '/' [',' [typedargslist_no_posonly]])|(typedargslist_no_posonly)" +# +# It needs to be fully expanded to allow our LL(1) parser to work on it. + +typedargslist: tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [ + ',' [((tfpdef ['=' test] ',')* ('*' [tname] (',' tname ['=' test])* + [',' ['**' tname [',']]] | '**' tname [',']) + | tfpdef ['=' test] (',' tfpdef ['=' test])* [','])] + ] | ((tfpdef ['=' test] ',')* ('*' [tname] (',' tname ['=' test])* + [',' ['**' tname [',']]] | '**' tname [',']) + | tfpdef ['=' test] (',' tfpdef ['=' test])* [',']) + tname: NAME [':' test] tfpdef: tname | '(' tfplist ')' tfplist: tfpdef (',' tfpdef)* [','] -varargslist: ((vfpdef ['=' test] ',')* - ('*' [vname] (',' vname ['=' test])* [',' ['**' vname [',']]] | '**' vname [',']) - | vfpdef ['=' test] (',' vfpdef ['=' test])* [',']) + +# The following definition for varargslist is equivalent to this set of rules: +# +# arguments = argument (',' argument )* +# argument = vfpdef ['=' test] +# kwargs = '**' vname [','] +# args = '*' [vname] +# kwonly_kwargs = (',' argument )* [',' [kwargs]] +# args_kwonly_kwargs = args kwonly_kwargs | kwargs +# poskeyword_args_kwonly_kwargs = arguments [',' [args_kwonly_kwargs]] +# vararglist_no_posonly = poskeyword_args_kwonly_kwargs | args_kwonly_kwargs +# varargslist = arguments ',' '/' [','[(vararglist_no_posonly)]] | (vararglist_no_posonly) +# +# It needs to be fully expanded to allow our LL(1) parser to work on it. + +varargslist: vfpdef ['=' test ](',' vfpdef ['=' test])* ',' '/' [',' [ + ((vfpdef ['=' test] ',')* ('*' [vname] (',' vname ['=' test])* + [',' ['**' vname [',']]] | '**' vname [',']) + | vfpdef ['=' test] (',' vfpdef ['=' test])* [',']) + ]] | ((vfpdef ['=' test] ',')* + ('*' [vname] (',' vname ['=' test])* [',' ['**' vname [',']]]| '**' vname [',']) + | vfpdef ['=' test] (',' vfpdef ['=' test])* [',']) + vname: NAME vfpdef: vname | '(' vfplist ')' vfplist: vfpdef (',' vfpdef)* [','] diff --git a/Lib/lib2to3/tests/test_parser.py b/Lib/lib2to3/tests/test_parser.py index ba2bb78733..d5db66b9b1 100644 --- a/Lib/lib2to3/tests/test_parser.py +++ b/Lib/lib2to3/tests/test_parser.py @@ -272,6 +272,12 @@ class TestUnpackingGeneralizations(GrammarTest): def test_dict_display_2(self): self.validate("""{**{}, 3:4, **{5:6, 7:8}}""") + def test_complex_star_expression(self): + self.validate("func(* [] or [1])") + + def test_complex_double_star_expression(self): + self.validate("func(**{1: 3} if False else {x: x for x in range(3)})") + def test_argument_unpacking_1(self): self.validate("""f(a, *b, *c, d)""") @@ -630,6 +636,7 @@ class TestLiterals(GrammarTest): class TestNamedAssignments(GrammarTest): + """Also known as the walrus operator.""" def test_named_assignment_if(self): driver.parse_string("if f := x(): pass\n") @@ -644,6 +651,30 @@ class TestNamedAssignments(GrammarTest): driver.parse_string("[(lastNum := num) == 1 for num in [1, 2, 3]]\n") +class TestPositionalOnlyArgs(GrammarTest): + + def test_one_pos_only_arg(self): + driver.parse_string("def one_pos_only_arg(a, /): pass\n") + + def test_all_markers(self): + driver.parse_string( + "def all_markers(a, b=2, /, c, d=4, *, e=5, f): pass\n") + + def test_all_with_args_and_kwargs(self): + driver.parse_string( + """def all_markers_with_args_and_kwargs( + aa, b, /, _cc, d, *args, e, f_f, **kwargs, + ): + pass\n""") + + def test_lambda_soup(self): + driver.parse_string( + "lambda a, b, /, c, d, *args, e, f, **kw: kw\n") + + def test_only_positional_or_keyword(self): + driver.parse_string("def func(a,b,/,*,g,e=3): pass\n") + + class TestPickleableException(unittest.TestCase): def test_ParseError(self): err = ParseError('msg', 2, None, (1, 'context')) diff --git a/Lib/linecache.py b/Lib/linecache.py index fa5dbd09ea..513b17e999 100644 --- a/Lib/linecache.py +++ b/Lib/linecache.py @@ -165,9 +165,14 @@ def lazycache(filename, module_globals): if not filename or (filename.startswith('<') and filename.endswith('>')): return False # Try for a __loader__, if available - if module_globals and '__loader__' in module_globals: - name = module_globals.get('__name__') - loader = module_globals['__loader__'] + if module_globals and '__name__' in module_globals: + name = module_globals['__name__'] + if (loader := module_globals.get('__loader__')) is None: + if spec := module_globals.get('__spec__'): + try: + loader = spec.loader + except AttributeError: + pass get_source = getattr(loader, 'get_source', None) if name and get_source: diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index badfd654b1..50b7378cd6 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1289,6 +1289,14 @@ class Manager(object): self.loggerClass = None self.logRecordFactory = None + @property + def disable(self): + return self._disable + + @disable.setter + def disable(self, value): + self._disable = _checkLevel(value) + def getLogger(self, name): """ Get a logger with the specified name (channel name), creating it diff --git a/Lib/opcode.py b/Lib/opcode.py index ac1aa535f6..cc321166e7 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -83,7 +83,6 @@ def_op('BINARY_TRUE_DIVIDE', 27) def_op('INPLACE_FLOOR_DIVIDE', 28) def_op('INPLACE_TRUE_DIVIDE', 29) -def_op('RERAISE', 48) def_op('WITH_EXCEPT_START', 49) def_op('GET_AITER', 50) def_op('GET_ANEXT', 51) @@ -161,6 +160,7 @@ name_op('LOAD_GLOBAL', 116) # Index in name list def_op('IS_OP', 117) def_op('CONTAINS_OP', 118) +def_op('RERAISE', 119) jabs_op('JUMP_IF_NOT_EXC_MATCH', 121) jrel_op('SETUP_FINALLY', 122) # Distance to target address @@ -36,7 +36,7 @@ _names = sys.builtin_module_names __all__ = ["altsep", "curdir", "pardir", "sep", "pathsep", "linesep", "defpath", "name", "path", "devnull", "SEEK_SET", "SEEK_CUR", "SEEK_END", "fsencode", "fsdecode", "get_exec_path", "fdopen", - "popen", "extsep"] + "extsep"] def _exists(name): return name in globals() @@ -969,51 +969,55 @@ otherwise return -SIG, where SIG is the signal that killed it. """ __all__.extend(["spawnlp", "spawnlpe"]) - -# Supply os.popen() -def popen(cmd, mode="r", buffering=-1): - if not isinstance(cmd, str): - raise TypeError("invalid cmd type (%s, expected string)" % type(cmd)) - if mode not in ("r", "w"): - raise ValueError("invalid mode %r" % mode) - if buffering == 0 or buffering is None: - raise ValueError("popen() does not support unbuffered streams") - import subprocess, io - if mode == "r": - proc = subprocess.Popen(cmd, - shell=True, - stdout=subprocess.PIPE, - bufsize=buffering) - return _wrap_close(io.TextIOWrapper(proc.stdout), proc) - else: - proc = subprocess.Popen(cmd, - shell=True, - stdin=subprocess.PIPE, - bufsize=buffering) - return _wrap_close(io.TextIOWrapper(proc.stdin), proc) - -# Helper for popen() -- a proxy for a file whose close waits for the process -class _wrap_close: - def __init__(self, stream, proc): - self._stream = stream - self._proc = proc - def close(self): - self._stream.close() - returncode = self._proc.wait() - if returncode == 0: - return None - if name == 'nt': - return returncode +# VxWorks has no user space shell provided. As a result, running +# command in a shell can't be supported. +if sys.platform != 'vxworks': + # Supply os.popen() + def popen(cmd, mode="r", buffering=-1): + if not isinstance(cmd, str): + raise TypeError("invalid cmd type (%s, expected string)" % type(cmd)) + if mode not in ("r", "w"): + raise ValueError("invalid mode %r" % mode) + if buffering == 0 or buffering is None: + raise ValueError("popen() does not support unbuffered streams") + import subprocess, io + if mode == "r": + proc = subprocess.Popen(cmd, + shell=True, + stdout=subprocess.PIPE, + bufsize=buffering) + return _wrap_close(io.TextIOWrapper(proc.stdout), proc) else: - return returncode << 8 # Shift left to match old behavior - def __enter__(self): - return self - def __exit__(self, *args): - self.close() - def __getattr__(self, name): - return getattr(self._stream, name) - def __iter__(self): - return iter(self._stream) + proc = subprocess.Popen(cmd, + shell=True, + stdin=subprocess.PIPE, + bufsize=buffering) + return _wrap_close(io.TextIOWrapper(proc.stdin), proc) + + # Helper for popen() -- a proxy for a file whose close waits for the process + class _wrap_close: + def __init__(self, stream, proc): + self._stream = stream + self._proc = proc + def close(self): + self._stream.close() + returncode = self._proc.wait() + if returncode == 0: + return None + if name == 'nt': + return returncode + else: + return returncode << 8 # Shift left to match old behavior + def __enter__(self): + return self + def __exit__(self, *args): + self.close() + def __getattr__(self, name): + return getattr(self._stream, name) + def __iter__(self): + return iter(self._stream) + + __all__.append("popen") # Supply os.fdopen() def fdopen(fd, *args, **kwargs): diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 178c5b981d..531a699a40 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -630,7 +630,10 @@ class _PathParents(Sequence): return len(self._parts) def __getitem__(self, idx): - if idx < 0 or idx >= len(self): + if isinstance(idx, slice): + return tuple(self[i] for i in range(*idx.indices(len(self)))) + + if idx >= len(self) or idx < -len(self): raise IndexError(idx) return self._pathcls._from_parsed_parts(self._drv, self._root, self._parts[:-idx - 1]) diff --git a/Lib/pickle.py b/Lib/pickle.py index cbac5f168b..e63a8b6e4d 100644 --- a/Lib/pickle.py +++ b/Lib/pickle.py @@ -340,7 +340,9 @@ def whichmodule(obj, name): # Protect the iteration by using a list copy of sys.modules against dynamic # modules that trigger imports of other modules upon calls to getattr. for module_name, module in sys.modules.copy().items(): - if module_name == '__main__' or module is None: + if (module_name == '__main__' + or module_name == '__mp_main__' # bpo-42406 + or module is None): continue try: if _getattribute(module, name)[0] is obj: diff --git a/Lib/platform.py b/Lib/platform.py index 0eb5167d58..1a07b44077 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -1230,6 +1230,63 @@ def platform(aliased=0, terse=0): _platform_cache[(aliased, terse)] = platform return platform +### freedesktop.org os-release standard +# https://www.freedesktop.org/software/systemd/man/os-release.html + +# NAME=value with optional quotes (' or "). The regular expression is less +# strict than shell lexer, but that's ok. +_os_release_line = re.compile( + "^(?P<name>[a-zA-Z0-9_]+)=(?P<quote>[\"\']?)(?P<value>.*)(?P=quote)$" +) +# unescape five special characters mentioned in the standard +_os_release_unescape = re.compile(r"\\([\\\$\"\'`])") +# /etc takes precedence over /usr/lib +_os_release_candidates = ("/etc/os-release", "/usr/lib/os-release") +_os_release_cache = None + + +def _parse_os_release(lines): + # These fields are mandatory fields with well-known defaults + # in pratice all Linux distributions override NAME, ID, and PRETTY_NAME. + info = { + "NAME": "Linux", + "ID": "linux", + "PRETTY_NAME": "Linux", + } + + for line in lines: + mo = _os_release_line.match(line) + if mo is not None: + info[mo.group('name')] = _os_release_unescape.sub( + r"\1", mo.group('value') + ) + + return info + + +def freedesktop_os_release(): + """Return operation system identification from freedesktop.org os-release + """ + global _os_release_cache + + if _os_release_cache is None: + errno = None + for candidate in _os_release_candidates: + try: + with open(candidate, encoding="utf-8") as f: + _os_release_cache = _parse_os_release(f) + break + except OSError as e: + errno = e.errno + else: + raise OSError( + errno, + f"Unable to read files {', '.join(_os_release_candidates)}" + ) + + return _os_release_cache.copy() + + ### Command line interface if __name__ == '__main__': diff --git a/Lib/plistlib.py b/Lib/plistlib.py index 83b214e9dc..2eeebe4c9a 100644 --- a/Lib/plistlib.py +++ b/Lib/plistlib.py @@ -611,7 +611,7 @@ def _count_to_size(count): elif count < 1 << 16: return 2 - elif count << 1 << 32: + elif count < 1 << 32: return 4 else: diff --git a/Lib/posixpath.py b/Lib/posixpath.py index ecb4e5a8f7..62afbd0ccf 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -262,6 +262,9 @@ def expanduser(path): # password database, return the path unchanged return path userhome = pwent.pw_dir + # if no user home, return the path unchanged on VxWorks + if userhome is None and sys.platform == "vxworks": + return path if isinstance(path, bytes): userhome = os.fsencode(userhome) root = b'/' diff --git a/Lib/pprint.py b/Lib/pprint.py index 213998e349..a8af50e5a6 100644 --- a/Lib/pprint.py +++ b/Lib/pprint.py @@ -64,15 +64,15 @@ def pp(object, *args, sort_dicts=False, **kwargs): def saferepr(object): """Version of repr() which can handle recursive data structures.""" - return _safe_repr(object, {}, None, 0, True)[0] + return PrettyPrinter()._safe_repr(object, {}, None, 0)[0] def isreadable(object): """Determine if saferepr(object) is readable by eval().""" - return _safe_repr(object, {}, None, 0, True)[1] + return PrettyPrinter()._safe_repr(object, {}, None, 0)[1] def isrecursive(object): """Determine if object requires a recursive representation.""" - return _safe_repr(object, {}, None, 0, True)[2] + return PrettyPrinter()._safe_repr(object, {}, None, 0)[2] class _safe_key: """Helper function for key functions when sorting unorderable objects. @@ -435,7 +435,7 @@ class PrettyPrinter: and flags indicating whether the representation is 'readable' and whether the object represents a recursive construct. """ - return _safe_repr(object, context, maxlevels, level, self._sort_dicts) + return self._safe_repr(object, context, maxlevels, level) def _pprint_default_dict(self, object, stream, indent, allowance, context, level): if not len(object): @@ -518,77 +518,79 @@ class PrettyPrinter: _dispatch[_collections.UserString.__repr__] = _pprint_user_string -# Return triple (repr_string, isreadable, isrecursive). + def _safe_repr(self, object, context, maxlevels, level): + # Return triple (repr_string, isreadable, isrecursive). + typ = type(object) + if typ in _builtin_scalars: + return repr(object), True, False -def _safe_repr(object, context, maxlevels, level, sort_dicts): - typ = type(object) - if typ in _builtin_scalars: - return repr(object), True, False - - r = getattr(typ, "__repr__", None) - if issubclass(typ, dict) and r is dict.__repr__: - if not object: - return "{}", True, False - objid = id(object) - if maxlevels and level >= maxlevels: - return "{...}", False, objid in context - if objid in context: - return _recursion(object), False, True - context[objid] = 1 - readable = True - recursive = False - components = [] - append = components.append - level += 1 - if sort_dicts: - items = sorted(object.items(), key=_safe_tuple) - else: - items = object.items() - for k, v in items: - krepr, kreadable, krecur = _safe_repr(k, context, maxlevels, level, sort_dicts) - vrepr, vreadable, vrecur = _safe_repr(v, context, maxlevels, level, sort_dicts) - append("%s: %s" % (krepr, vrepr)) - readable = readable and kreadable and vreadable - if krecur or vrecur: - recursive = True - del context[objid] - return "{%s}" % ", ".join(components), readable, recursive - - if (issubclass(typ, list) and r is list.__repr__) or \ - (issubclass(typ, tuple) and r is tuple.__repr__): - if issubclass(typ, list): + r = getattr(typ, "__repr__", None) + if issubclass(typ, dict) and r is dict.__repr__: if not object: - return "[]", True, False - format = "[%s]" - elif len(object) == 1: - format = "(%s,)" - else: - if not object: - return "()", True, False - format = "(%s)" - objid = id(object) - if maxlevels and level >= maxlevels: - return format % "...", False, objid in context - if objid in context: - return _recursion(object), False, True - context[objid] = 1 - readable = True - recursive = False - components = [] - append = components.append - level += 1 - for o in object: - orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level, sort_dicts) - append(orepr) - if not oreadable: - readable = False - if orecur: - recursive = True - del context[objid] - return format % ", ".join(components), readable, recursive - - rep = repr(object) - return rep, (rep and not rep.startswith('<')), False + return "{}", True, False + objid = id(object) + if maxlevels and level >= maxlevels: + return "{...}", False, objid in context + if objid in context: + return _recursion(object), False, True + context[objid] = 1 + readable = True + recursive = False + components = [] + append = components.append + level += 1 + if self._sort_dicts: + items = sorted(object.items(), key=_safe_tuple) + else: + items = object.items() + for k, v in items: + krepr, kreadable, krecur = self.format( + k, context, maxlevels, level) + vrepr, vreadable, vrecur = self.format( + v, context, maxlevels, level) + append("%s: %s" % (krepr, vrepr)) + readable = readable and kreadable and vreadable + if krecur or vrecur: + recursive = True + del context[objid] + return "{%s}" % ", ".join(components), readable, recursive + + if (issubclass(typ, list) and r is list.__repr__) or \ + (issubclass(typ, tuple) and r is tuple.__repr__): + if issubclass(typ, list): + if not object: + return "[]", True, False + format = "[%s]" + elif len(object) == 1: + format = "(%s,)" + else: + if not object: + return "()", True, False + format = "(%s)" + objid = id(object) + if maxlevels and level >= maxlevels: + return format % "...", False, objid in context + if objid in context: + return _recursion(object), False, True + context[objid] = 1 + readable = True + recursive = False + components = [] + append = components.append + level += 1 + for o in object: + orepr, oreadable, orecur = self.format( + o, context, maxlevels, level) + append(orepr) + if not oreadable: + readable = False + if orecur: + recursive = True + del context[objid] + return format % ", ".join(components), readable, recursive + + rep = repr(object) + return rep, (rep and not rep.startswith('<')), False _builtin_scalars = frozenset({str, bytes, bytearray, int, float, complex, bool, type(None)}) @@ -604,7 +606,7 @@ def _perfcheck(object=None): object = [("string", (1, 2), [3, 4], {5: 6, 7: 8})] * 100000 p = PrettyPrinter() t1 = time.perf_counter() - _safe_repr(object, {}, None, 0, True) + p._safe_repr(object, {}, None, 0, True) t2 = time.perf_counter() p.pformat(object) t3 = time.perf_counter() diff --git a/Lib/pyclbr.py b/Lib/pyclbr.py index 99a17343fb..f0c8381946 100644 --- a/Lib/pyclbr.py +++ b/Lib/pyclbr.py @@ -25,7 +25,9 @@ has the following attributes: children -- nested objects contained in this object. The 'children' attribute is a dictionary mapping names to objects. -Instances of Function describe functions with the attributes from _Object. +Instances of Function describe functions with the attributes from _Object, +plus the following: + is_async -- if a function is defined with an 'async' prefix Instances of Class describe classes with the attributes from _Object, plus the following: @@ -38,11 +40,10 @@ are recognized and imported modules are scanned as well, this shouldn't happen often. """ -import io +import ast +import copy import sys import importlib.util -import tokenize -from token import NAME, DEDENT, OP __all__ = ["readmodule", "readmodule_ex", "Class", "Function"] @@ -58,41 +59,33 @@ class _Object: self.lineno = lineno self.parent = parent self.children = {} - - def _addchild(self, name, obj): - self.children[name] = obj - + if parent is not None: + parent.children[name] = self class Function(_Object): "Information about a Python function, including methods." - def __init__(self, module, name, file, lineno, parent=None): - _Object.__init__(self, module, name, file, lineno, parent) - + def __init__(self, module, name, file, lineno, parent=None, is_async=False): + super().__init__(module, name, file, lineno, parent) + self.is_async = is_async + if isinstance(parent, Class): + parent.methods[name] = lineno class Class(_Object): "Information about a Python class." - def __init__(self, module, name, super, file, lineno, parent=None): - _Object.__init__(self, module, name, file, lineno, parent) - self.super = [] if super is None else super + def __init__(self, module, name, super_, file, lineno, parent=None): + super().__init__(module, name, file, lineno, parent) + self.super = super_ or [] self.methods = {} - def _addmethod(self, name, lineno): - self.methods[name] = lineno - - -def _nest_function(ob, func_name, lineno): +# These 2 functions are used in these tests +# Lib/test/test_pyclbr, Lib/idlelib/idle_test/test_browser.py +def _nest_function(ob, func_name, lineno, is_async=False): "Return a Function after nesting within ob." - newfunc = Function(ob.module, func_name, ob.file, lineno, ob) - ob._addchild(func_name, newfunc) - if isinstance(ob, Class): - ob._addmethod(func_name, lineno) - return newfunc + return Function(ob.module, func_name, ob.file, lineno, ob, is_async) def _nest_class(ob, class_name, lineno, super=None): "Return a Class after nesting within ob." - newclass = Class(ob.module, class_name, super, ob.file, lineno, ob) - ob._addchild(class_name, newclass) - return newclass + return Class(ob.module, class_name, super, ob.file, lineno, ob) def readmodule(module, path=None): """Return Class objects for the top-level classes in module. @@ -179,187 +172,95 @@ def _readmodule(module, path, inpackage=None): return _create_tree(fullmodule, path, fname, source, tree, inpackage) -def _create_tree(fullmodule, path, fname, source, tree, inpackage): - """Return the tree for a particular module. - - fullmodule (full module name), inpackage+module, becomes o.module. - path is passed to recursive calls of _readmodule. - fname becomes o.file. - source is tokenized. Imports cause recursive calls to _readmodule. - tree is {} or {'__path__': <submodule search locations>}. - inpackage, None or string, is passed to recursive calls of _readmodule. - - The effect of recursive calls is mutation of global _modules. - """ - f = io.StringIO(source) +class _ModuleBrowser(ast.NodeVisitor): + def __init__(self, module, path, file, tree, inpackage): + self.path = path + self.tree = tree + self.file = file + self.module = module + self.inpackage = inpackage + self.stack = [] + + def visit_ClassDef(self, node): + bases = [] + for base in node.bases: + name = ast.unparse(base) + if name in self.tree: + # We know this super class. + bases.append(self.tree[name]) + elif len(names := name.split(".")) > 1: + # Super class form is module.class: + # look in module for class. + *_, module, class_ = names + if module in _modules: + bases.append(_modules[module].get(class_, name)) + else: + bases.append(name) + + parent = self.stack[-1] if self.stack else None + class_ = Class( + self.module, node.name, bases, self.file, node.lineno, parent + ) + if parent is None: + self.tree[node.name] = class_ + self.stack.append(class_) + self.generic_visit(node) + self.stack.pop() + + def visit_FunctionDef(self, node, *, is_async=False): + parent = self.stack[-1] if self.stack else None + function = Function( + self.module, node.name, self.file, node.lineno, parent, is_async + ) + if parent is None: + self.tree[node.name] = function + self.stack.append(function) + self.generic_visit(node) + self.stack.pop() + + def visit_AsyncFunctionDef(self, node): + self.visit_FunctionDef(node, is_async=True) + + def visit_Import(self, node): + if node.col_offset != 0: + return + + for module in node.names: + try: + try: + _readmodule(module.name, self.path, self.inpackage) + except ImportError: + _readmodule(module.name, []) + except (ImportError, SyntaxError): + # If we can't find or parse the imported module, + # too bad -- don't die here. + continue + + def visit_ImportFrom(self, node): + if node.col_offset != 0: + return + try: + module = "." * node.level + if node.module: + module += node.module + module = _readmodule(module, self.path, self.inpackage) + except (ImportError, SyntaxError): + return + + for name in node.names: + if name.name in module: + self.tree[name.asname or name.name] = module[name.name] + elif name.name == "*": + for import_name, import_value in module.items(): + if import_name.startswith("_"): + continue + self.tree[import_name] = import_value - stack = [] # Initialize stack of (class, indent) pairs. - g = tokenize.generate_tokens(f.readline) - try: - for tokentype, token, start, _end, _line in g: - if tokentype == DEDENT: - lineno, thisindent = start - # Close previous nested classes and defs. - while stack and stack[-1][1] >= thisindent: - del stack[-1] - elif token == 'def': - lineno, thisindent = start - # Close previous nested classes and defs. - while stack and stack[-1][1] >= thisindent: - del stack[-1] - tokentype, func_name, start = next(g)[0:3] - if tokentype != NAME: - continue # Skip def with syntax error. - cur_func = None - if stack: - cur_obj = stack[-1][0] - cur_func = _nest_function(cur_obj, func_name, lineno) - else: - # It is just a function. - cur_func = Function(fullmodule, func_name, fname, lineno) - tree[func_name] = cur_func - stack.append((cur_func, thisindent)) - elif token == 'class': - lineno, thisindent = start - # Close previous nested classes and defs. - while stack and stack[-1][1] >= thisindent: - del stack[-1] - tokentype, class_name, start = next(g)[0:3] - if tokentype != NAME: - continue # Skip class with syntax error. - # Parse what follows the class name. - tokentype, token, start = next(g)[0:3] - inherit = None - if token == '(': - names = [] # Initialize list of superclasses. - level = 1 - super = [] # Tokens making up current superclass. - while True: - tokentype, token, start = next(g)[0:3] - if token in (')', ',') and level == 1: - n = "".join(super) - if n in tree: - # We know this super class. - n = tree[n] - else: - c = n.split('.') - if len(c) > 1: - # Super class form is module.class: - # look in module for class. - m = c[-2] - c = c[-1] - if m in _modules: - d = _modules[m] - if c in d: - n = d[c] - names.append(n) - super = [] - if token == '(': - level += 1 - elif token == ')': - level -= 1 - if level == 0: - break - elif token == ',' and level == 1: - pass - # Only use NAME and OP (== dot) tokens for type name. - elif tokentype in (NAME, OP) and level == 1: - super.append(token) - # Expressions in the base list are not supported. - inherit = names - if stack: - cur_obj = stack[-1][0] - cur_class = _nest_class( - cur_obj, class_name, lineno, inherit) - else: - cur_class = Class(fullmodule, class_name, inherit, - fname, lineno) - tree[class_name] = cur_class - stack.append((cur_class, thisindent)) - elif token == 'import' and start[1] == 0: - modules = _getnamelist(g) - for mod, _mod2 in modules: - try: - # Recursively read the imported module. - if inpackage is None: - _readmodule(mod, path) - else: - try: - _readmodule(mod, path, inpackage) - except ImportError: - _readmodule(mod, []) - except: - # If we can't find or parse the imported module, - # too bad -- don't die here. - pass - elif token == 'from' and start[1] == 0: - mod, token = _getname(g) - if not mod or token != "import": - continue - names = _getnamelist(g) - try: - # Recursively read the imported module. - d = _readmodule(mod, path, inpackage) - except: - # If we can't find or parse the imported module, - # too bad -- don't die here. - continue - # Add any classes that were defined in the imported module - # to our name space if they were mentioned in the list. - for n, n2 in names: - if n in d: - tree[n2 or n] = d[n] - elif n == '*': - # Don't add names that start with _. - for n in d: - if n[0] != '_': - tree[n] = d[n] - except StopIteration: - pass - - f.close() - return tree - - -def _getnamelist(g): - """Return list of (dotted-name, as-name or None) tuples for token source g. - - An as-name is the name that follows 'as' in an as clause. - """ - names = [] - while True: - name, token = _getname(g) - if not name: - break - if token == 'as': - name2, token = _getname(g) - else: - name2 = None - names.append((name, name2)) - while token != "," and "\n" not in token: - token = next(g)[1] - if token != ",": - break - return names - - -def _getname(g): - "Return (dotted-name or None, next-token) tuple for token source g." - parts = [] - tokentype, token = next(g)[0:2] - if tokentype != NAME and token != '*': - return (None, token) - parts.append(token) - while True: - tokentype, token = next(g)[0:2] - if token != '.': - break - tokentype, token = next(g)[0:2] - if tokentype != NAME: - break - parts.append(token) - return (".".join(parts), token) +def _create_tree(fullmodule, path, fname, source, tree, inpackage): + mbrowser = _ModuleBrowser(fullmodule, path, fname, tree, inpackage) + mbrowser.visit(ast.parse(source)) + return mbrowser.tree def _main(): diff --git a/Lib/pydoc_data/topics.py b/Lib/pydoc_data/topics.py index 1fdb1ae859..49630bb4b8 100644 --- a/Lib/pydoc_data/topics.py +++ b/Lib/pydoc_data/topics.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Autogenerated by Sphinx on Mon Oct 5 18:27:28 2020 +# Autogenerated by Sphinx on Mon Dec 7 19:34:00 2020 topics = {'assert': 'The "assert" statement\n' '**********************\n' '\n' @@ -433,11 +433,9 @@ topics = {'assert': 'The "assert" statement\n' '\n' 'Execution of Python coroutines can be suspended and resumed at ' 'many\n' - 'points (see *coroutine*). Inside the body of a coroutine ' - 'function,\n' - '"await" and "async" identifiers become reserved keywords; "await"\n' - 'expressions, "async for" and "async with" can only be used in\n' - 'coroutine function bodies.\n' + 'points (see *coroutine*). "await" expressions, "async for" and ' + '"async\n' + 'with" can only be used in the body of a coroutine function.\n' '\n' 'Functions defined with "async def" syntax are always coroutine\n' 'functions, even if they do not contain "await" or "async" ' @@ -453,6 +451,10 @@ topics = {'assert': 'The "assert" statement\n' ' do_stuff()\n' ' await some_coroutine()\n' '\n' + 'Changed in version 3.7: "await" and "async" are now keywords;\n' + 'previously they were only treated as such inside the body of a\n' + 'coroutine function.\n' + '\n' '\n' 'The "async for" statement\n' '=========================\n' @@ -700,6 +702,11 @@ topics = {'assert': 'The "assert" statement\n' 'syntax or\n' ' built-in functions. See Special method lookup.\n' '\n' + ' For certain sensitive attribute accesses, raises an ' + 'auditing event\n' + ' "object.__getattr__" with arguments "obj" and ' + '"name".\n' + '\n' 'object.__setattr__(self, name, value)\n' '\n' ' Called when an attribute assignment is attempted. ' @@ -716,6 +723,11 @@ topics = {'assert': 'The "assert" statement\n' 'for example,\n' ' "object.__setattr__(self, name, value)".\n' '\n' + ' For certain sensitive attribute assignments, raises ' + 'an auditing\n' + ' event "object.__setattr__" with arguments "obj", ' + '"name", "value".\n' + '\n' 'object.__delattr__(self, name)\n' '\n' ' Like "__setattr__()" but for attribute deletion ' @@ -724,6 +736,11 @@ topics = {'assert': 'The "assert" statement\n' 'obj.name" is\n' ' meaningful for the object.\n' '\n' + ' For certain sensitive attribute deletions, raises an ' + 'auditing event\n' + ' "object.__delattr__" with arguments "obj" and ' + '"name".\n' + '\n' 'object.__dir__(self)\n' '\n' ' Called when "dir()" is called on the object. A ' @@ -1464,8 +1481,8 @@ topics = {'assert': 'The "assert" statement\n' '\n' ' Called when the instance is “called†as a function; if ' 'this method\n' - ' is defined, "x(arg1, arg2, ...)" is a shorthand for\n' - ' "x.__call__(arg1, arg2, ...)".\n', + ' is defined, "x(arg1, arg2, ...)" roughly translates to\n' + ' "type(x).__call__(x, arg1, ...)".\n', 'calls': 'Calls\n' '*****\n' '\n' @@ -2766,20 +2783,11 @@ topics = {'assert': 'The "assert" statement\n' 'parameter list. These annotations can be any valid Python ' 'expression.\n' 'The presence of annotations does not change the semantics of a\n' - 'function. The annotation values are available as values of a\n' + 'function. The annotation values are available as string values ' + 'in a\n' 'dictionary keyed by the parameters’ names in the ' '"__annotations__"\n' - 'attribute of the function object. If the "annotations" import ' - 'from\n' - '"__future__" is used, annotations are preserved as strings at ' - 'runtime\n' - 'which enables postponed evaluation. Otherwise, they are ' - 'evaluated\n' - 'when the function definition is executed. In this case ' - 'annotations\n' - 'may be evaluated in a different order than they appear in the ' - 'source\n' - 'code.\n' + 'attribute of the function object.\n' '\n' 'It is also possible to create anonymous functions (functions not ' 'bound\n' @@ -2949,12 +2957,9 @@ topics = {'assert': 'The "assert" statement\n' '\n' 'Execution of Python coroutines can be suspended and resumed at ' 'many\n' - 'points (see *coroutine*). Inside the body of a coroutine ' - 'function,\n' - '"await" and "async" identifiers become reserved keywords; ' - '"await"\n' - 'expressions, "async for" and "async with" can only be used in\n' - 'coroutine function bodies.\n' + 'points (see *coroutine*). "await" expressions, "async for" and ' + '"async\n' + 'with" can only be used in the body of a coroutine function.\n' '\n' 'Functions defined with "async def" syntax are always coroutine\n' 'functions, even if they do not contain "await" or "async" ' @@ -2970,6 +2975,10 @@ topics = {'assert': 'The "assert" statement\n' ' do_stuff()\n' ' await some_coroutine()\n' '\n' + 'Changed in version 3.7: "await" and "async" are now keywords;\n' + 'previously they were only treated as such inside the body of a\n' + 'coroutine function.\n' + '\n' '\n' 'The "async for" statement\n' '-------------------------\n' @@ -3461,16 +3470,21 @@ topics = {'assert': 'The "assert" statement\n' ' on the value to determine if the result is true or ' 'false.\n' '\n' - ' By default, "__ne__()" delegates to "__eq__()" and ' - 'inverts the\n' - ' result unless it is "NotImplemented". There are no ' - 'other implied\n' - ' relationships among the comparison operators, for ' - 'example, the\n' - ' truth of "(x<y or x==y)" does not imply "x<=y". To ' - 'automatically\n' - ' generate ordering operations from a single root ' - 'operation, see\n' + ' By default, "object" implements "__eq__()" by using ' + '"is", returning\n' + ' "NotImplemented" in the case of a false comparison: ' + '"True if x is y\n' + ' else NotImplemented". For "__ne__()", by default it ' + 'delegates to\n' + ' "__eq__()" and inverts the result unless it is ' + '"NotImplemented".\n' + ' There are no other implied relationships among the ' + 'comparison\n' + ' operators or default implementations; for example, the ' + 'truth of\n' + ' "(x<y or x==y)" does not imply "x<=y". To automatically ' + 'generate\n' + ' ordering operations from a single root operation, see\n' ' "functools.total_ordering()".\n' '\n' ' See the paragraph on "__hash__()" for some important ' @@ -5287,24 +5301,23 @@ topics = {'assert': 'The "assert" statement\n' 'for the\n' 'conversion. The alternate form is defined differently for ' 'different\n' - 'types. This option is only valid for integer, float, ' - 'complex and\n' - 'Decimal types. For integers, when binary, octal, or ' - 'hexadecimal output\n' - 'is used, this option adds the prefix respective "\'0b\'", ' - '"\'0o\'", or\n' - '"\'0x\'" to the output value. For floats, complex and ' - 'Decimal the\n' - 'alternate form causes the result of the conversion to ' - 'always contain a\n' - 'decimal-point character, even if no digits follow it. ' - 'Normally, a\n' - 'decimal-point character appears in the result of these ' - 'conversions\n' - 'only if a digit follows it. In addition, for "\'g\'" and ' - '"\'G\'"\n' - 'conversions, trailing zeros are not removed from the ' - 'result.\n' + 'types. This option is only valid for integer, float and ' + 'complex\n' + 'types. For integers, when binary, octal, or hexadecimal ' + 'output is\n' + 'used, this option adds the prefix respective "\'0b\'", ' + '"\'0o\'", or "\'0x\'"\n' + 'to the output value. For float and complex the alternate ' + 'form causes\n' + 'the result of the conversion to always contain a ' + 'decimal-point\n' + 'character, even if no digits follow it. Normally, a ' + 'decimal-point\n' + 'character appears in the result of these conversions only ' + 'if a digit\n' + 'follows it. In addition, for "\'g\'" and "\'G\'" ' + 'conversions, trailing\n' + 'zeros are not removed from the result.\n' '\n' 'The "\',\'" option signals the use of a comma for a ' 'thousands separator.\n' @@ -5442,9 +5455,8 @@ topics = {'assert': 'The "assert" statement\n' 'the integer\n' 'to a floating point number before formatting.\n' '\n' - 'The available presentation types for floating point and ' - 'decimal values\n' - 'are:\n' + 'The available presentation types for "float" and "Decimal" ' + 'values are:\n' '\n' ' ' '+-----------+------------------------------------------------------------+\n' @@ -5453,24 +5465,50 @@ topics = {'assert': 'The "assert" statement\n' '|\n' ' ' '|===========|============================================================|\n' - ' | "\'e\'" | Exponent notation. Prints the number in ' - 'scientific |\n' - ' | | notation using the letter ‘e’ to indicate ' - 'the exponent. |\n' - ' | | The default precision is ' - '"6". |\n' + ' | "\'e\'" | Scientific notation. For a given ' + 'precision "p", formats |\n' + ' | | the number in scientific notation with the ' + 'letter ‘e’ |\n' + ' | | separating the coefficient from the ' + 'exponent. The |\n' + ' | | coefficient has one digit before and "p" ' + 'digits after the |\n' + ' | | decimal point, for a total of "p + 1" ' + 'significant digits. |\n' + ' | | With no precision given, uses a precision ' + 'of "6" digits |\n' + ' | | after the decimal point for "float", and ' + 'shows all |\n' + ' | | coefficient digits for "Decimal". If no ' + 'digits follow the |\n' + ' | | decimal point, the decimal point is also ' + 'removed unless |\n' + ' | | the "#" option is ' + 'used. |\n' ' ' '+-----------+------------------------------------------------------------+\n' - ' | "\'E\'" | Exponent notation. Same as "\'e\'" ' - 'except it uses an upper |\n' + ' | "\'E\'" | Scientific notation. Same as "\'e\'" ' + 'except it uses an upper |\n' ' | | case ‘E’ as the separator ' 'character. |\n' ' ' '+-----------+------------------------------------------------------------+\n' - ' | "\'f\'" | Fixed-point notation. Displays the ' - 'number as a fixed-point |\n' - ' | | number. The default precision is ' - '"6". |\n' + ' | "\'f\'" | Fixed-point notation. For a given ' + 'precision "p", formats |\n' + ' | | the number as a decimal number with ' + 'exactly "p" digits |\n' + ' | | following the decimal point. With no ' + 'precision given, uses |\n' + ' | | a precision of "6" digits after the ' + 'decimal point for |\n' + ' | | "float", and uses a precision large enough ' + 'to show all |\n' + ' | | coefficient digits for "Decimal". If no ' + 'digits follow the |\n' + ' | | decimal point, the decimal point is also ' + 'removed unless |\n' + ' | | the "#" option is ' + 'used. |\n' ' ' '+-----------+------------------------------------------------------------+\n' ' | "\'F\'" | Fixed-point notation. Same as "\'f\'", ' @@ -5516,9 +5554,14 @@ topics = {'assert': 'The "assert" statement\n' ' | | regardless of the precision. A precision ' 'of "0" is |\n' ' | | treated as equivalent to a precision of ' - '"1". The default |\n' - ' | | precision is ' - '"6". |\n' + '"1". With no |\n' + ' | | precision given, uses a precision of "6" ' + 'significant |\n' + ' | | digits for "float", and shows all ' + 'coefficient digits for |\n' + ' | | ' + '"Decimal". ' + '|\n' ' ' '+-----------+------------------------------------------------------------+\n' ' | "\'G\'" | General format. Same as "\'g\'" except ' @@ -5859,20 +5902,11 @@ topics = {'assert': 'The "assert" statement\n' 'parameter list. These annotations can be any valid Python ' 'expression.\n' 'The presence of annotations does not change the semantics of a\n' - 'function. The annotation values are available as values of a\n' + 'function. The annotation values are available as string values ' + 'in a\n' 'dictionary keyed by the parameters’ names in the ' '"__annotations__"\n' - 'attribute of the function object. If the "annotations" import ' - 'from\n' - '"__future__" is used, annotations are preserved as strings at ' - 'runtime\n' - 'which enables postponed evaluation. Otherwise, they are ' - 'evaluated\n' - 'when the function definition is executed. In this case ' - 'annotations\n' - 'may be evaluated in a different order than they appear in the ' - 'source\n' - 'code.\n' + 'attribute of the function object.\n' '\n' 'It is also possible to create anonymous functions (functions not ' 'bound\n' @@ -6395,8 +6429,8 @@ topics = {'assert': 'The "assert" statement\n' '\n' '* other future statements.\n' '\n' - 'The only feature in Python 3.7 that requires using the future\n' - 'statement is "annotations".\n' + 'The only feature that requires using the future statement is\n' + '"annotations" (see **PEP 563**).\n' '\n' 'All historical features enabled by the future statement are still\n' 'recognized by Python 3. The list includes "absolute_import",\n' @@ -8242,16 +8276,21 @@ topics = {'assert': 'The "assert" statement\n' ' on the value to determine if the result is true or ' 'false.\n' '\n' - ' By default, "__ne__()" delegates to "__eq__()" and ' - 'inverts the\n' - ' result unless it is "NotImplemented". There are no other ' - 'implied\n' - ' relationships among the comparison operators, for ' - 'example, the\n' - ' truth of "(x<y or x==y)" does not imply "x<=y". To ' - 'automatically\n' - ' generate ordering operations from a single root ' - 'operation, see\n' + ' By default, "object" implements "__eq__()" by using "is", ' + 'returning\n' + ' "NotImplemented" in the case of a false comparison: "True ' + 'if x is y\n' + ' else NotImplemented". For "__ne__()", by default it ' + 'delegates to\n' + ' "__eq__()" and inverts the result unless it is ' + '"NotImplemented".\n' + ' There are no other implied relationships among the ' + 'comparison\n' + ' operators or default implementations; for example, the ' + 'truth of\n' + ' "(x<y or x==y)" does not imply "x<=y". To automatically ' + 'generate\n' + ' ordering operations from a single root operation, see\n' ' "functools.total_ordering()".\n' '\n' ' See the paragraph on "__hash__()" for some important ' @@ -8481,6 +8520,10 @@ topics = {'assert': 'The "assert" statement\n' 'syntax or\n' ' built-in functions. See Special method lookup.\n' '\n' + ' For certain sensitive attribute accesses, raises an ' + 'auditing event\n' + ' "object.__getattr__" with arguments "obj" and "name".\n' + '\n' 'object.__setattr__(self, name, value)\n' '\n' ' Called when an attribute assignment is attempted. This ' @@ -8497,6 +8540,11 @@ topics = {'assert': 'The "assert" statement\n' 'example,\n' ' "object.__setattr__(self, name, value)".\n' '\n' + ' For certain sensitive attribute assignments, raises an ' + 'auditing\n' + ' event "object.__setattr__" with arguments "obj", "name", ' + '"value".\n' + '\n' 'object.__delattr__(self, name)\n' '\n' ' Like "__setattr__()" but for attribute deletion instead ' @@ -8505,6 +8553,10 @@ topics = {'assert': 'The "assert" statement\n' 'obj.name" is\n' ' meaningful for the object.\n' '\n' + ' For certain sensitive attribute deletions, raises an ' + 'auditing event\n' + ' "object.__delattr__" with arguments "obj" and "name".\n' + '\n' 'object.__dir__(self)\n' '\n' ' Called when "dir()" is called on the object. A sequence ' @@ -9298,8 +9350,8 @@ topics = {'assert': 'The "assert" statement\n' '\n' ' Called when the instance is “called†as a function; if ' 'this method\n' - ' is defined, "x(arg1, arg2, ...)" is a shorthand for\n' - ' "x.__call__(arg1, arg2, ...)".\n' + ' is defined, "x(arg1, arg2, ...)" roughly translates to\n' + ' "type(x).__call__(x, arg1, ...)".\n' '\n' '\n' 'Emulating container types\n' @@ -11054,9 +11106,10 @@ topics = {'assert': 'The "assert" statement\n' 'subscriptions': 'Subscriptions\n' '*************\n' '\n' - 'A subscription selects an item of a sequence (string, tuple ' - 'or list)\n' - 'or mapping (dictionary) object:\n' + 'Subscription of a sequence (string, tuple or list) or ' + 'mapping\n' + '(dictionary) object usually selects an item from the ' + 'collection:\n' '\n' ' subscription ::= primary "[" expression_list "]"\n' '\n' @@ -11107,7 +11160,13 @@ topics = {'assert': 'The "assert" statement\n' '\n' 'A string’s items are characters. A character is not a ' 'separate data\n' - 'type but a string of exactly one character.\n', + 'type but a string of exactly one character.\n' + '\n' + 'Subscription of certain *classes* or *types* creates a ' + 'generic alias.\n' + 'In this case, user-defined classes can support subscription ' + 'by\n' + 'providing a "__class_getitem__()" classmethod.\n', 'truth': 'Truth Value Testing\n' '*******************\n' '\n' @@ -11353,6 +11412,27 @@ topics = {'assert': 'The "assert" statement\n' 'representation\n' ' in computers.\n' '\n' + ' The string representations of the numeric classes, computed by\n' + ' "__repr__()" and "__str__()", have the following properties:\n' + '\n' + ' * They are valid numeric literals which, when passed to their ' + 'class\n' + ' constructor, produce an object having the value of the ' + 'original\n' + ' numeric.\n' + '\n' + ' * The representation is in base 10, when possible.\n' + '\n' + ' * Leading zeros, possibly excepting a single zero before a ' + 'decimal\n' + ' point, are not shown.\n' + '\n' + ' * Trailing zeros, possibly excepting a single zero after a ' + 'decimal\n' + ' point, are not shown.\n' + '\n' + ' * A sign is shown only when the number is negative.\n' + '\n' ' Python distinguishes between integers, floating point numbers, ' 'and\n' ' complex numbers:\n' @@ -12404,6 +12484,21 @@ topics = {'assert': 'The "assert" statement\n' 'positional\n' ' argument and a possibly empty set of keyword arguments.\n' '\n' + ' Dictionaries can be created by several means:\n' + '\n' + ' * Use a comma-separated list of "key: value" pairs within ' + 'braces:\n' + ' "{\'jack\': 4098, \'sjoerd\': 4127}" or "{4098: ' + "'jack', 4127:\n" + ' \'sjoerd\'}"\n' + '\n' + ' * Use a dict comprehension: "{}", "{x: x ** 2 for x in ' + 'range(10)}"\n' + '\n' + ' * Use the type constructor: "dict()", "dict([(\'foo\', ' + "100), ('bar',\n" + ' 200)])", "dict(foo=100, bar=200)"\n' + '\n' ' If no positional argument is given, an empty dictionary ' 'is created.\n' ' If a positional argument is given and it is a mapping ' diff --git a/Lib/random.py b/Lib/random.py index 139e8a40bb..66433baa8c 100644 --- a/Lib/random.py +++ b/Lib/random.py @@ -424,13 +424,14 @@ class Random(_random.Random): # too many calls to _randbelow(), making them slower and # causing them to eat more entropy than necessary. - if isinstance(population, _Set): - _warn('Sampling from a set deprecated\n' - 'since Python 3.9 and will be removed in a subsequent version.', - DeprecationWarning, 2) - population = tuple(population) if not isinstance(population, _Sequence): - raise TypeError("Population must be a sequence. For dicts or sets, use sorted(d).") + if isinstance(population, _Set): + _warn('Sampling from a set deprecated\n' + 'since Python 3.9 and will be removed in a subsequent version.', + DeprecationWarning, 2) + population = tuple(population) + else: + raise TypeError("Population must be a sequence. For dicts or sets, use sorted(d).") n = len(population) if counts is not None: cum_counts = list(_accumulate(counts)) diff --git a/Lib/shutil.py b/Lib/shutil.py index 223e9a8a70..f0e833dc97 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -711,7 +711,7 @@ def rmtree(path, ignore_errors=False, onerror=None): try: fd = os.open(path, os.O_RDONLY) except Exception: - onerror(os.lstat, path, sys.exc_info()) + onerror(os.open, path, sys.exc_info()) return try: if os.path.samestat(orig_st, os.fstat(fd)): diff --git a/Lib/site.py b/Lib/site.py index 4d3b869fff..5f1b31e73d 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -105,8 +105,15 @@ def makepath(*paths): def abs_paths(): """Set all module __file__ and __cached__ attributes to an absolute path""" for m in set(sys.modules.values()): - if (getattr(getattr(m, '__loader__', None), '__module__', None) not in - ('_frozen_importlib', '_frozen_importlib_external')): + loader_module = None + try: + loader_module = m.__loader__.__module__ + except AttributeError: + try: + loader_module = m.__spec__.loader.__module__ + except AttributeError: + pass + if loader_module not in {'_frozen_importlib', '_frozen_importlib_external'}: continue # don't mess with a PEP 302-supplied __file__ try: m.__file__ = os.path.abspath(m.__file__) @@ -257,6 +264,10 @@ def _getuserbase(): if env_base: return env_base + # VxWorks has no home directories + if sys.platform == "vxworks": + return None + def joinuser(*args): return os.path.expanduser(os.path.join(*args)) @@ -304,11 +315,14 @@ def getusersitepackages(): If the global variable ``USER_SITE`` is not initialized yet, this function will also set it. """ - global USER_SITE + global USER_SITE, ENABLE_USER_SITE userbase = getuserbase() # this will also set USER_BASE if USER_SITE is None: - USER_SITE = _get_path(userbase) + if userbase is None: + ENABLE_USER_SITE = False # disable user site and return None + else: + USER_SITE = _get_path(userbase) return USER_SITE @@ -623,11 +637,14 @@ def _script(): for dir in sys.path: print(" %r," % (dir,)) print("]") - print("USER_BASE: %r (%s)" % (user_base, - "exists" if os.path.isdir(user_base) else "doesn't exist")) - print("USER_SITE: %r (%s)" % (user_site, - "exists" if os.path.isdir(user_site) else "doesn't exist")) - print("ENABLE_USER_SITE: %r" % ENABLE_USER_SITE) + def exists(path): + if path is not None and os.path.isdir(path): + return "exists" + else: + return "doesn't exist" + print(f"USER_BASE: {user_base!r} ({exists(user_base)})") + print(f"USER_SITE: {user_site!r} ({exists(user_site)})") + print(f"ENABLE_USER_SITE: {ENABLE_USER_SITE!r}") sys.exit(0) buffer = [] diff --git a/Lib/smtpd.py b/Lib/smtpd.py index 8f1a22e937..1e2adc87a2 100755 --- a/Lib/smtpd.py +++ b/Lib/smtpd.py @@ -163,7 +163,7 @@ class SMTPChannel(asynchat.async_chat): # a race condition may occur if the other end is closing # before we can get the peername self.close() - if err.args[0] != errno.ENOTCONN: + if err.errno != errno.ENOTCONN: raise return print('Peer:', repr(self.peer), file=DEBUGSTREAM) diff --git a/Lib/socket.py b/Lib/socket.py index cafa573a30..5276cc8ba3 100755 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -377,7 +377,7 @@ class socket(_socket.socket): try: while True: if timeout and not selector_select(timeout): - raise _socket.timeout('timed out') + raise TimeoutError('timed out') if count: blocksize = count - total_sent if blocksize <= 0: @@ -706,7 +706,7 @@ class SocketIO(io.RawIOBase): self._timeout_occurred = True raise except error as e: - if e.args[0] in _blocking_errnos: + if e.errno in _blocking_errnos: return None raise @@ -722,7 +722,7 @@ class SocketIO(io.RawIOBase): return self._sock.send(b) except error as e: # XXX what about EINTR? - if e.args[0] in _blocking_errnos: + if e.errno in _blocking_errnos: return None raise diff --git a/Lib/sqlite3/__init__.py b/Lib/sqlite3/__init__.py index 6c91df27cc..f001c0678e 100644 --- a/Lib/sqlite3/__init__.py +++ b/Lib/sqlite3/__init__.py @@ -21,3 +21,17 @@ # 3. This notice may not be removed or altered from any source distribution. from sqlite3.dbapi2 import * + + +# bpo-42264: OptimizedUnicode was deprecated in Python 3.10. It's scheduled +# for removal in Python 3.12. +def __getattr__(name): + if name == "OptimizedUnicode": + import warnings + msg = (""" + OptimizedUnicode is deprecated and will be removed in Python 3.12. + Since Python 3.3 it has simply been an alias for 'str'. + """) + warnings.warn(msg, DeprecationWarning, stacklevel=2) + return str + raise AttributeError(f"module 'sqlite3' has no attribute '{name}'") diff --git a/Lib/sqlite3/test/factory.py b/Lib/sqlite3/test/factory.py index 95dd24bdfa..d91997333b 100644 --- a/Lib/sqlite3/test/factory.py +++ b/Lib/sqlite3/test/factory.py @@ -254,9 +254,10 @@ class TextFactoryTests(unittest.TestCase): self.assertTrue(row[0].endswith("reich"), "column must contain original data") def CheckOptimizedUnicode(self): - # In py3k, str objects are always returned when text_factory - # is OptimizedUnicode - self.con.text_factory = sqlite.OptimizedUnicode + # OptimizedUnicode is deprecated as of Python 3.10 + with self.assertWarns(DeprecationWarning) as cm: + self.con.text_factory = sqlite.OptimizedUnicode + self.assertIn("factory.py", cm.filename) austria = "Österreich" germany = "Deutchland" a_row = self.con.execute("select ?", (austria,)).fetchone() diff --git a/Lib/statistics.py b/Lib/statistics.py index f9d3802ec5..4b054b9611 100644 --- a/Lib/statistics.py +++ b/Lib/statistics.py @@ -106,7 +106,7 @@ import random from fractions import Fraction from decimal import Decimal -from itertools import groupby +from itertools import groupby, repeat from bisect import bisect_left, bisect_right from math import hypot, sqrt, fabs, exp, erf, tau, log, fsum from operator import itemgetter @@ -364,37 +364,37 @@ def geometric_mean(data): ' containing positive numbers') from None -def harmonic_mean(data): +def harmonic_mean(data, weights=None): """Return the harmonic mean of data. The harmonic mean, sometimes called the subcontrary mean, is the reciprocal of the arithmetic mean of the reciprocals of the data, and is often appropriate when averaging quantities which are rates - or ratios, for example speeds. Example: + or ratios, for example speeds. - Suppose an investor purchases an equal value of shares in each of - three companies, with P/E (price/earning) ratios of 2.5, 3 and 10. - What is the average P/E ratio for the investor's portfolio? + Suppose a car travels 40 km/hr for 5 km and then speeds-up to + 60 km/hr for another 5 km. What is the average speed? - >>> harmonic_mean([2.5, 3, 10]) # For an equal investment portfolio. - 3.6 + >>> harmonic_mean([40, 60]) + 48.0 - Using the arithmetic mean would give an average of about 5.167, which - is too high. + Suppose a car travels 40 km/hr for 5 km, and when traffic clears, + speeds-up to 60 km/hr for the remaining 30 km of the journey. What + is the average speed? + + >>> harmonic_mean([40, 60], weights=[5, 30]) + 56.0 If ``data`` is empty, or any element is less than zero, ``harmonic_mean`` will raise ``StatisticsError``. """ - # For a justification for using harmonic mean for P/E ratios, see - # http://fixthepitch.pellucid.com/comps-analysis-the-missing-harmony-of-summary-statistics/ - # http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2621087 if iter(data) is data: data = list(data) errmsg = 'harmonic mean does not support negative values' n = len(data) if n < 1: raise StatisticsError('harmonic_mean requires at least one data point') - elif n == 1: + elif n == 1 and weights is None: x = data[0] if isinstance(x, (numbers.Real, Decimal)): if x < 0: @@ -402,13 +402,23 @@ def harmonic_mean(data): return x else: raise TypeError('unsupported type') + if weights is None: + weights = repeat(1, n) + sum_weights = n + else: + if iter(weights) is weights: + weights = list(weights) + if len(weights) != n: + raise StatisticsError('Number of weights does not match data size') + _, sum_weights, _ = _sum(w for w in _fail_neg(weights, errmsg)) try: - T, total, count = _sum(1 / x for x in _fail_neg(data, errmsg)) + data = _fail_neg(data, errmsg) + T, total, count = _sum(w / x if w else 0 for w, x in zip(weights, data)) except ZeroDivisionError: return 0 - assert count == n - return _convert(n / total, T) - + if total <= 0: + raise StatisticsError('Weighted sum must be positive') + return _convert(sum_weights / total, T) # FIXME: investigate ways to calculate medians without sorting? Quickselect? def median(data): diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 6a6c2fc98e..aa107cb60e 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -420,7 +420,11 @@ def check_output(*popenargs, timeout=None, **kwargs): if 'input' in kwargs and kwargs['input'] is None: # Explicitly passing input=None was previously equivalent to passing an # empty string. That is maintained here for backwards compatibility. - kwargs['input'] = '' if kwargs.get('universal_newlines', False) else b'' + if kwargs.get('universal_newlines') or kwargs.get('text'): + empty = '' + else: + empty = b'' + kwargs['input'] = empty return run(*popenargs, stdout=PIPE, timeout=timeout, check=True, **kwargs).stdout @@ -2078,7 +2082,11 @@ class Popen(object): # The race condition can still happen if the race condition # described above happens between the returncode test # and the kill() call. - os.kill(self.pid, sig) + try: + os.kill(self.pid, sig) + except ProcessLookupError: + # Supress the race condition error; bpo-40550. + pass def terminate(self): """Terminate the process with SIGTERM diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py index 6c87b06634..c1aaf79a67 100644 --- a/Lib/sysconfig.py +++ b/Lib/sysconfig.py @@ -51,34 +51,65 @@ _INSTALL_SCHEMES = { 'scripts': '{base}/Scripts', 'data': '{base}', }, - # NOTE: When modifying "purelib" scheme, update site._get_path() too. - 'nt_user': { - 'stdlib': '{userbase}/Python{py_version_nodot_plat}', - 'platstdlib': '{userbase}/Python{py_version_nodot_plat}', - 'purelib': '{userbase}/Python{py_version_nodot_plat}/site-packages', - 'platlib': '{userbase}/Python{py_version_nodot_plat}/site-packages', - 'include': '{userbase}/Python{py_version_nodot_plat}/Include', - 'scripts': '{userbase}/Python{py_version_nodot_plat}/Scripts', - 'data': '{userbase}', - }, - 'posix_user': { - 'stdlib': '{userbase}/{platlibdir}/python{py_version_short}', - 'platstdlib': '{userbase}/{platlibdir}/python{py_version_short}', - 'purelib': '{userbase}/lib/python{py_version_short}/site-packages', - 'platlib': '{userbase}/{platlibdir}/python{py_version_short}/site-packages', - 'include': '{userbase}/include/python{py_version_short}', - 'scripts': '{userbase}/bin', - 'data': '{userbase}', - }, - 'osx_framework_user': { - 'stdlib': '{userbase}/lib/python', - 'platstdlib': '{userbase}/lib/python', - 'purelib': '{userbase}/lib/python/site-packages', - 'platlib': '{userbase}/lib/python/site-packages', - 'include': '{userbase}/include', - 'scripts': '{userbase}/bin', - 'data': '{userbase}', - }, + } + + +# NOTE: site.py has copy of this function. +# Sync it when modify this function. +def _getuserbase(): + env_base = os.environ.get("PYTHONUSERBASE", None) + if env_base: + return env_base + + # VxWorks has no home directories + if sys.platform == "vxworks": + return None + + def joinuser(*args): + return os.path.expanduser(os.path.join(*args)) + + if os.name == "nt": + base = os.environ.get("APPDATA") or "~" + return joinuser(base, "Python") + + if sys.platform == "darwin" and sys._framework: + return joinuser("~", "Library", sys._framework, + "%d.%d" % sys.version_info[:2]) + + return joinuser("~", ".local") + +_HAS_USER_BASE = (_getuserbase() is not None) + +if _HAS_USER_BASE: + _INSTALL_SCHEMES |= { + # NOTE: When modifying "purelib" scheme, update site._get_path() too. + 'nt_user': { + 'stdlib': '{userbase}/Python{py_version_nodot_plat}', + 'platstdlib': '{userbase}/Python{py_version_nodot_plat}', + 'purelib': '{userbase}/Python{py_version_nodot_plat}/site-packages', + 'platlib': '{userbase}/Python{py_version_nodot_plat}/site-packages', + 'include': '{userbase}/Python{py_version_nodot_plat}/Include', + 'scripts': '{userbase}/Python{py_version_nodot_plat}/Scripts', + 'data': '{userbase}', + }, + 'posix_user': { + 'stdlib': '{userbase}/{platlibdir}/python{py_version_short}', + 'platstdlib': '{userbase}/{platlibdir}/python{py_version_short}', + 'purelib': '{userbase}/lib/python{py_version_short}/site-packages', + 'platlib': '{userbase}/{platlibdir}/python{py_version_short}/site-packages', + 'include': '{userbase}/include/python{py_version_short}', + 'scripts': '{userbase}/bin', + 'data': '{userbase}', + }, + 'osx_framework_user': { + 'stdlib': '{userbase}/lib/python', + 'platstdlib': '{userbase}/lib/python', + 'purelib': '{userbase}/lib/python/site-packages', + 'platlib': '{userbase}/lib/python/site-packages', + 'include': '{userbase}/include', + 'scripts': '{userbase}/bin', + 'data': '{userbase}', + }, } _SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include', @@ -183,25 +214,6 @@ def _get_default_scheme(): return os.name -# NOTE: site.py has copy of this function. -# Sync it when modify this function. -def _getuserbase(): - env_base = os.environ.get("PYTHONUSERBASE", None) - if env_base: - return env_base - - def joinuser(*args): - return os.path.expanduser(os.path.join(*args)) - - if os.name == "nt": - base = os.environ.get("APPDATA") or "~" - return joinuser(base, "Python") - - if sys.platform == "darwin" and sys._framework: - return joinuser("~", "Library", sys._framework, - "%d.%d" % sys.version_info[:2]) - - return joinuser("~", ".local") def _parse_makefile(filename, vars=None): @@ -424,10 +436,11 @@ def _init_posix(vars): def _init_non_posix(vars): """Initialize the module as appropriate for NT""" # set basic install directories + import _imp vars['LIBDEST'] = get_path('stdlib') vars['BINLIBDEST'] = get_path('platstdlib') vars['INCLUDEPY'] = get_path('include') - vars['EXT_SUFFIX'] = '.pyd' + vars['EXT_SUFFIX'] = _imp.extension_suffixes()[0] vars['EXE'] = '.exe' vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable)) @@ -557,10 +570,11 @@ def get_config_vars(*args): SO = _CONFIG_VARS.get('EXT_SUFFIX') if SO is not None: _CONFIG_VARS['SO'] = SO - # Setting 'userbase' is done below the call to the - # init function to enable using 'get_config_var' in - # the init-function. - _CONFIG_VARS['userbase'] = _getuserbase() + if _HAS_USER_BASE: + # Setting 'userbase' is done below the call to the + # init function to enable using 'get_config_var' in + # the init-function. + _CONFIG_VARS['userbase'] = _getuserbase() # Always convert srcdir to an absolute path srcdir = _CONFIG_VARS.get('srcdir', _PROJECT_BASE) diff --git a/Lib/tarfile.py b/Lib/tarfile.py index e42279470d..395c0f1d30 100755 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -200,6 +200,7 @@ def itn(n, digits=8, format=DEFAULT_FORMAT): # base-256 representation. This allows values up to (256**(digits-1))-1. # A 0o200 byte indicates a positive number, a 0o377 byte a negative # number. + original_n = n n = int(n) if 0 <= n < 8 ** (digits - 1): s = bytes("%0*o" % (digits - 1, n), "ascii") + NUL @@ -363,7 +364,7 @@ class _Stream: try: import zlib except ImportError: - raise CompressionError("zlib module is not available") + raise CompressionError("zlib module is not available") from None self.zlib = zlib self.crc = zlib.crc32(b"") if mode == "r": @@ -376,7 +377,7 @@ class _Stream: try: import bz2 except ImportError: - raise CompressionError("bz2 module is not available") + raise CompressionError("bz2 module is not available") from None if mode == "r": self.dbuf = b"" self.cmp = bz2.BZ2Decompressor() @@ -388,7 +389,7 @@ class _Stream: try: import lzma except ImportError: - raise CompressionError("lzma module is not available") + raise CompressionError("lzma module is not available") from None if mode == "r": self.dbuf = b"" self.cmp = lzma.LZMADecompressor() @@ -541,8 +542,8 @@ class _Stream: break try: buf = self.cmp.decompress(buf) - except self.exception: - raise ReadError("invalid compressed data") + except self.exception as e: + raise ReadError("invalid compressed data") from e t.append(buf) c += len(buf) t = b"".join(t) @@ -1164,8 +1165,8 @@ class TarInfo(object): # Fetch the next header and process it. try: next = self.fromtarfile(tarfile) - except HeaderError: - raise SubsequentHeaderError("missing or bad subsequent header") + except HeaderError as e: + raise SubsequentHeaderError(str(e)) from None # Patch the TarInfo object from the next header with # the longname information. @@ -1277,8 +1278,8 @@ class TarInfo(object): # Fetch the next header. try: next = self.fromtarfile(tarfile) - except HeaderError: - raise SubsequentHeaderError("missing or bad subsequent header") + except HeaderError as e: + raise SubsequentHeaderError(str(e)) from None # Process GNU sparse information. if "GNU.sparse.map" in pax_headers: @@ -1533,7 +1534,7 @@ class TarFile(object): self.fileobj.seek(self.offset) break except HeaderError as e: - raise ReadError(str(e)) + raise ReadError(str(e)) from None if self.mode in ("a", "w", "x"): self._loaded = True @@ -1669,21 +1670,21 @@ class TarFile(object): try: from gzip import GzipFile except ImportError: - raise CompressionError("gzip module is not available") + raise CompressionError("gzip module is not available") from None try: fileobj = GzipFile(name, mode + "b", compresslevel, fileobj) - except OSError: + except OSError as e: if fileobj is not None and mode == 'r': - raise ReadError("not a gzip file") + raise ReadError("not a gzip file") from e raise try: t = cls.taropen(name, mode, fileobj, **kwargs) - except OSError: + except OSError as e: fileobj.close() if mode == 'r': - raise ReadError("not a gzip file") + raise ReadError("not a gzip file") from e raise except: fileobj.close() @@ -1702,16 +1703,16 @@ class TarFile(object): try: from bz2 import BZ2File except ImportError: - raise CompressionError("bz2 module is not available") + raise CompressionError("bz2 module is not available") from None fileobj = BZ2File(fileobj or name, mode, compresslevel=compresslevel) try: t = cls.taropen(name, mode, fileobj, **kwargs) - except (OSError, EOFError): + except (OSError, EOFError) as e: fileobj.close() if mode == 'r': - raise ReadError("not a bzip2 file") + raise ReadError("not a bzip2 file") from e raise except: fileobj.close() @@ -1730,16 +1731,16 @@ class TarFile(object): try: from lzma import LZMAFile, LZMAError except ImportError: - raise CompressionError("lzma module is not available") + raise CompressionError("lzma module is not available") from None fileobj = LZMAFile(fileobj or name, mode, preset=preset) try: t = cls.taropen(name, mode, fileobj, **kwargs) - except (LZMAError, EOFError): + except (LZMAError, EOFError) as e: fileobj.close() if mode == 'r': - raise ReadError("not an lzma file") + raise ReadError("not an lzma file") from e raise except: fileobj.close() @@ -2237,6 +2238,9 @@ class TarFile(object): try: # For systems that support symbolic and hard links. if tarinfo.issym(): + if os.path.lexists(targetpath): + # Avoid FileExistsError on following os.symlink. + os.unlink(targetpath) os.symlink(tarinfo.linkname, targetpath) else: # See extract(). @@ -2250,7 +2254,7 @@ class TarFile(object): self._extract_member(self._find_link_target(tarinfo), targetpath) except KeyError: - raise ExtractError("unable to resolve link inside archive") + raise ExtractError("unable to resolve link inside archive") from None def chown(self, tarinfo, targetpath, numeric_owner): """Set owner of targetpath according to tarinfo. If numeric_owner @@ -2278,16 +2282,16 @@ class TarFile(object): os.lchown(targetpath, u, g) else: os.chown(targetpath, u, g) - except OSError: - raise ExtractError("could not change owner") + except OSError as e: + raise ExtractError("could not change owner") from e def chmod(self, tarinfo, targetpath): """Set file permissions of targetpath according to tarinfo. """ try: os.chmod(targetpath, tarinfo.mode) - except OSError: - raise ExtractError("could not change mode") + except OSError as e: + raise ExtractError("could not change mode") from e def utime(self, tarinfo, targetpath): """Set modification time of targetpath according to tarinfo. @@ -2296,8 +2300,8 @@ class TarFile(object): return try: os.utime(targetpath, (tarinfo.mtime, tarinfo.mtime)) - except OSError: - raise ExtractError("could not change modification time") + except OSError as e: + raise ExtractError("could not change modification time") from e #-------------------------------------------------------------------------- def next(self): @@ -2333,15 +2337,15 @@ class TarFile(object): self.offset += BLOCKSIZE continue elif self.offset == 0: - raise ReadError(str(e)) + raise ReadError(str(e)) from None except EmptyHeaderError: if self.offset == 0: - raise ReadError("empty file") + raise ReadError("empty file") from None except TruncatedHeaderError as e: if self.offset == 0: - raise ReadError(str(e)) + raise ReadError(str(e)) from None except SubsequentHeaderError as e: - raise ReadError(str(e)) + raise ReadError(str(e)) from None break if tarinfo is not None: diff --git a/Lib/test/_test_atexit.py b/Lib/test/_test_atexit.py new file mode 100644 index 0000000000..a316585311 --- /dev/null +++ b/Lib/test/_test_atexit.py @@ -0,0 +1,121 @@ +""" +Tests run by test_atexit in a subprocess since it clears atexit callbacks. +""" +import atexit +import sys +import unittest +from test import support + + +class GeneralTest(unittest.TestCase): + def setUp(self): + atexit._clear() + + def tearDown(self): + atexit._clear() + + def assert_raises_unraisable(self, exc_type, func, *args): + with support.catch_unraisable_exception() as cm: + atexit.register(func, *args) + atexit._run_exitfuncs() + + self.assertEqual(cm.unraisable.object, func) + self.assertEqual(cm.unraisable.exc_type, exc_type) + self.assertEqual(type(cm.unraisable.exc_value), exc_type) + + def test_order(self): + # Check that callbacks are called in reverse order with the expected + # positional and keyword arguments. + calls = [] + + def func1(*args, **kwargs): + calls.append(('func1', args, kwargs)) + + def func2(*args, **kwargs): + calls.append(('func2', args, kwargs)) + + # be sure args are handled properly + atexit.register(func1, 1, 2) + atexit.register(func2) + atexit.register(func2, 3, key="value") + atexit._run_exitfuncs() + + self.assertEqual(calls, + [('func2', (3,), {'key': 'value'}), + ('func2', (), {}), + ('func1', (1, 2), {})]) + + def test_badargs(self): + def func(): + pass + + # func() has no parameter, but it's called with 2 parameters + self.assert_raises_unraisable(TypeError, func, 1 ,2) + + def test_raise(self): + def raise_type_error(): + raise TypeError + + self.assert_raises_unraisable(TypeError, raise_type_error) + + def test_raise_unnormalized(self): + # bpo-10756: Make sure that an unnormalized exception is handled + # properly. + def div_zero(): + 1 / 0 + + self.assert_raises_unraisable(ZeroDivisionError, div_zero) + + def test_exit(self): + self.assert_raises_unraisable(SystemExit, sys.exit) + + def test_stress(self): + a = [0] + def inc(): + a[0] += 1 + + for i in range(128): + atexit.register(inc) + atexit._run_exitfuncs() + + self.assertEqual(a[0], 128) + + def test_clear(self): + a = [0] + def inc(): + a[0] += 1 + + atexit.register(inc) + atexit._clear() + atexit._run_exitfuncs() + + self.assertEqual(a[0], 0) + + def test_unregister(self): + a = [0] + def inc(): + a[0] += 1 + def dec(): + a[0] -= 1 + + for i in range(4): + atexit.register(inc) + atexit.register(dec) + atexit.unregister(inc) + atexit._run_exitfuncs() + + self.assertEqual(a[0], -1) + + def test_bound_methods(self): + l = [] + atexit.register(l.append, 5) + atexit._run_exitfuncs() + self.assertEqual(l, [5]) + + atexit.unregister(l.append) + atexit._run_exitfuncs() + self.assertEqual(l, [5]) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/eintrdata/eintr_tester.py b/Lib/test/_test_eintr.py index e43b59d064..e43b59d064 100644 --- a/Lib/test/eintrdata/eintr_tester.py +++ b/Lib/test/_test_eintr.py diff --git a/Lib/test/_test_embed_set_config.py b/Lib/test/_test_embed_set_config.py new file mode 100644 index 0000000000..82c5d82982 --- /dev/null +++ b/Lib/test/_test_embed_set_config.py @@ -0,0 +1,261 @@ +# bpo-42260: Test _PyInterpreterState_GetConfigCopy() +# and _PyInterpreterState_SetConfig(). +# +# Test run in a subinterpreter since set_config(get_config()) +# does reset sys attributes to their state of the Python startup +# (before the site module is run). + +import _testinternalcapi +import os +import sys +import unittest + + +MS_WINDOWS = (os.name == 'nt') +MAX_HASH_SEED = 4294967295 + +class SetConfigTests(unittest.TestCase): + def setUp(self): + self.old_config = _testinternalcapi.get_config() + self.sys_copy = dict(sys.__dict__) + + def tearDown(self): + _testinternalcapi.set_config(self.old_config) + sys.__dict__.clear() + sys.__dict__.update(self.sys_copy) + + def set_config(self, **kwargs): + _testinternalcapi.set_config(self.old_config | kwargs) + + def check(self, **kwargs): + self.set_config(**kwargs) + for key, value in kwargs.items(): + self.assertEqual(getattr(sys, key), value, + (key, value)) + + def test_set_invalid(self): + invalid_uint = -1 + NULL = None + invalid_wstr = NULL + # PyWideStringList strings must be non-NULL + invalid_wstrlist = ["abc", NULL, "def"] + + type_tests = [] + value_tests = [ + # enum + ('_config_init', 0), + ('_config_init', 4), + # unsigned long + ("hash_seed", -1), + ("hash_seed", MAX_HASH_SEED + 1), + ] + + # int (unsigned) + options = [ + '_config_init', + 'isolated', + 'use_environment', + 'dev_mode', + 'install_signal_handlers', + 'use_hash_seed', + 'faulthandler', + 'tracemalloc', + 'import_time', + 'show_ref_count', + 'dump_refs', + 'malloc_stats', + 'parse_argv', + 'site_import', + 'bytes_warning', + 'inspect', + 'interactive', + 'optimization_level', + 'parser_debug', + 'write_bytecode', + 'verbose', + 'quiet', + 'user_site_directory', + 'configure_c_stdio', + 'buffered_stdio', + 'pathconfig_warnings', + 'module_search_paths_set', + 'skip_source_first_line', + '_install_importlib', + '_init_main', + '_isolated_interpreter', + ] + if MS_WINDOWS: + options.append('legacy_windows_stdio') + for key in options: + value_tests.append((key, invalid_uint)) + type_tests.append((key, "abc")) + type_tests.append((key, 2.0)) + + # wchar_t* + for key in ( + 'filesystem_encoding', + 'filesystem_errors', + 'stdio_encoding', + 'stdio_errors', + 'check_hash_pycs_mode', + 'program_name', + 'platlibdir', + # optional wstr: + # 'pythonpath_env' + # 'home' + # 'pycache_prefix' + # 'run_command' + # 'run_module' + # 'run_filename' + # 'executable' + # 'prefix' + # 'exec_prefix' + # 'base_executable' + # 'base_prefix' + # 'base_exec_prefix' + ): + value_tests.append((key, invalid_wstr)) + type_tests.append((key, b'bytes')) + type_tests.append((key, 123)) + + # PyWideStringList + for key in ( + 'orig_argv', + 'argv', + 'xoptions', + 'warnoptions', + 'module_search_paths', + ): + value_tests.append((key, invalid_wstrlist)) + type_tests.append((key, 123)) + type_tests.append((key, "abc")) + type_tests.append((key, [123])) + type_tests.append((key, [b"bytes"])) + + + if MS_WINDOWS: + value_tests.append(('legacy_windows_stdio', invalid_uint)) + + for exc_type, tests in ( + (ValueError, value_tests), + (TypeError, type_tests), + ): + for key, value in tests: + config = self.old_config | {key: value} + with self.subTest(key=key, value=value, exc_type=exc_type): + with self.assertRaises(exc_type): + _testinternalcapi.set_config(config) + + def test_flags(self): + for sys_attr, key, value in ( + ("debug", "parser_debug", 1), + ("inspect", "inspect", 2), + ("interactive", "interactive", 3), + ("optimize", "optimization_level", 4), + ("verbose", "verbose", 1), + ("bytes_warning", "bytes_warning", 10), + ("quiet", "quiet", 11), + ("isolated", "isolated", 12), + ): + with self.subTest(sys=sys_attr, key=key, value=value): + self.set_config(**{key: value, 'parse_argv': 0}) + self.assertEqual(getattr(sys.flags, sys_attr), value) + + self.set_config(write_bytecode=0) + self.assertEqual(sys.flags.dont_write_bytecode, True) + self.assertEqual(sys.dont_write_bytecode, True) + + self.set_config(write_bytecode=1) + self.assertEqual(sys.flags.dont_write_bytecode, False) + self.assertEqual(sys.dont_write_bytecode, False) + + self.set_config(user_site_directory=0, isolated=0) + self.assertEqual(sys.flags.no_user_site, 1) + self.set_config(user_site_directory=1, isolated=0) + self.assertEqual(sys.flags.no_user_site, 0) + + self.set_config(site_import=0) + self.assertEqual(sys.flags.no_site, 1) + self.set_config(site_import=1) + self.assertEqual(sys.flags.no_site, 0) + + self.set_config(dev_mode=0) + self.assertEqual(sys.flags.dev_mode, False) + self.set_config(dev_mode=1) + self.assertEqual(sys.flags.dev_mode, True) + + self.set_config(use_environment=0, isolated=0) + self.assertEqual(sys.flags.ignore_environment, 1) + self.set_config(use_environment=1, isolated=0) + self.assertEqual(sys.flags.ignore_environment, 0) + + self.set_config(use_hash_seed=1, hash_seed=0) + self.assertEqual(sys.flags.hash_randomization, 0) + self.set_config(use_hash_seed=0, hash_seed=0) + self.assertEqual(sys.flags.hash_randomization, 1) + self.set_config(use_hash_seed=1, hash_seed=123) + self.assertEqual(sys.flags.hash_randomization, 1) + + def test_options(self): + self.check(warnoptions=[]) + self.check(warnoptions=["default", "ignore"]) + + self.set_config(xoptions=[]) + self.assertEqual(sys._xoptions, {}) + self.set_config(xoptions=["dev", "tracemalloc=5"]) + self.assertEqual(sys._xoptions, {"dev": True, "tracemalloc": "5"}) + + def test_pathconfig(self): + self.check( + executable='executable', + prefix="prefix", + base_prefix="base_prefix", + exec_prefix="exec_prefix", + base_exec_prefix="base_exec_prefix", + platlibdir="platlibdir") + + self.set_config(base_executable="base_executable") + self.assertEqual(sys._base_executable, "base_executable") + + # When base_xxx is NULL, value is copied from xxxx + self.set_config( + executable='executable', + prefix="prefix", + exec_prefix="exec_prefix", + base_executable=None, + base_prefix=None, + base_exec_prefix=None) + self.assertEqual(sys._base_executable, "executable") + self.assertEqual(sys.base_prefix, "prefix") + self.assertEqual(sys.base_exec_prefix, "exec_prefix") + + def test_path(self): + self.set_config(module_search_paths_set=1, + module_search_paths=['a', 'b', 'c']) + self.assertEqual(sys.path, ['a', 'b', 'c']) + + # Leave sys.path unchanged if module_search_paths_set=0 + self.set_config(module_search_paths_set=0, + module_search_paths=['new_path']) + self.assertEqual(sys.path, ['a', 'b', 'c']) + + def test_argv(self): + self.set_config(parse_argv=0, + argv=['python_program', 'args'], + orig_argv=['orig', 'orig_args']) + self.assertEqual(sys.argv, ['python_program', 'args']) + self.assertEqual(sys.orig_argv, ['orig', 'orig_args']) + + self.set_config(parse_argv=0, + argv=[], + orig_argv=[]) + self.assertEqual(sys.argv, ['']) + self.assertEqual(sys.orig_argv, []) + + def test_pycache_prefix(self): + self.check(pycache_prefix=None) + self.check(pycache_prefix="pycache_prefix") + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/crashers/bogus_code_obj.py b/Lib/test/crashers/bogus_code_obj.py index 198d229491..e71b3582cf 100644 --- a/Lib/test/crashers/bogus_code_obj.py +++ b/Lib/test/crashers/bogus_code_obj.py @@ -14,6 +14,6 @@ the user build or load random bytecodes anyway. Otherwise, this is a import types -co = types.CodeType(0, 0, 0, 0, 0, b'\x04\x71\x00\x00', +co = types.CodeType(0, 0, 0, 0, 0, 0, b'\x04\x00\x71\x00', (), (), (), '', '', 1, b'') exec(co) diff --git a/Lib/test/inspect_fodder.py b/Lib/test/inspect_fodder.py index 96a0257bfd..e1287a3159 100644 --- a/Lib/test/inspect_fodder.py +++ b/Lib/test/inspect_fodder.py @@ -91,3 +91,25 @@ class Callable: custom_method = Callable().as_method_of(42) del Callable + +# line 95 +class WhichComments: + # line 97 + # before f + def f(self): + # line 100 + # start f + return 1 + # line 103 + # end f + # line 105 + # after f + + # before asyncf - line 108 + async def asyncf(self): + # start asyncf + return 2 + # end asyncf + # after asyncf - line 113 + # end of WhichComments - line 114 + # after WhichComments - line 115 diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 4ba749454c..5a45d78be9 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -421,7 +421,7 @@ is_jython = sys.platform.startswith('java') is_android = hasattr(sys, 'getandroidapilevel') -if sys.platform != 'win32': +if sys.platform not in ('win32', 'vxworks'): unix_shell = '/system/bin/sh' if is_android else '/bin/sh' else: unix_shell = None diff --git a/Lib/test/support/bytecode_helper.py b/Lib/test/support/bytecode_helper.py index 348e277c16..471d4a68f9 100644 --- a/Lib/test/support/bytecode_helper.py +++ b/Lib/test/support/bytecode_helper.py @@ -35,7 +35,8 @@ class BytecodeTestCase(unittest.TestCase): disassembly = self.get_disassembly_as_string(x) if argval is _UNSPECIFIED: msg = '%s occurs in bytecode:\n%s' % (opname, disassembly) + self.fail(msg) elif instr.argval == argval: msg = '(%s,%r) occurs in bytecode:\n%s' msg = msg % (opname, argval, disassembly) - self.fail(msg) + self.fail(msg) diff --git a/Lib/test/support/script_helper.py b/Lib/test/support/script_helper.py index 09bb586dcf..6d699c8486 100644 --- a/Lib/test/support/script_helper.py +++ b/Lib/test/support/script_helper.py @@ -11,12 +11,14 @@ import py_compile import zipfile from importlib.util import source_from_cache +from test import support from test.support.import_helper import make_legacy_pyc # Cached result of the expensive test performed in the function below. __cached_interp_requires_environment = None + def interpreter_requires_environment(): """ Returns True if our sys.executable interpreter requires environment @@ -136,12 +138,14 @@ def run_python_until_end(*args, **env_vars): rc = proc.returncode return _PythonRunResult(rc, out, err), cmd_line + def _assert_python(expected_success, /, *args, **env_vars): res, cmd_line = run_python_until_end(*args, **env_vars) if (res.rc and expected_success) or (not res.rc and not expected_success): res.fail(cmd_line) return res + def assert_python_ok(*args, **env_vars): """ Assert that running the interpreter with `args` and optional environment @@ -155,6 +159,7 @@ def assert_python_ok(*args, **env_vars): """ return _assert_python(True, *args, **env_vars) + def assert_python_failure(*args, **env_vars): """ Assert that running the interpreter with `args` and optional environment @@ -165,6 +170,7 @@ def assert_python_failure(*args, **env_vars): """ return _assert_python(False, *args, **env_vars) + def spawn_python(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): """Run a Python subprocess with the given arguments. @@ -187,6 +193,7 @@ def spawn_python(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): stdout=stdout, stderr=stderr, **kw) + def kill_python(p): """Run the given Popen process until completion and return stdout.""" p.stdin.close() @@ -198,6 +205,7 @@ def kill_python(p): subprocess._cleanup() return data + def make_script(script_dir, script_basename, source, omit_suffix=False): script_filename = script_basename if not omit_suffix: @@ -209,6 +217,7 @@ def make_script(script_dir, script_basename, source, omit_suffix=False): importlib.invalidate_caches() return script_name + def make_zip_script(zip_dir, zip_basename, script_name, name_in_zip=None): zip_filename = zip_basename+os.extsep+'zip' zip_name = os.path.join(zip_dir, zip_filename) @@ -228,10 +237,12 @@ def make_zip_script(zip_dir, zip_basename, script_name, name_in_zip=None): # zip_file.printdir() return zip_name, os.path.join(zip_name, name_in_zip) + def make_pkg(pkg_dir, init_source=''): os.mkdir(pkg_dir) make_script(pkg_dir, '__init__', init_source) + def make_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename, source, depth=1, compiled=False): unlink = [] @@ -260,3 +271,24 @@ def make_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename, # print 'Contents of %r:' % zip_name # zip_file.printdir() return zip_name, os.path.join(zip_name, script_name_in_zip) + + +def run_test_script(script): + # use -u to try to get the full output if the test hangs or crash + if support.verbose: + def title(text): + return f"===== {text} ======" + + name = f"script {os.path.basename(script)}" + print() + print(title(name), flush=True) + # In verbose mode, the child process inherit stdout and stdout, + # to see output in realtime and reduce the risk of losing output. + args = [sys.executable, "-E", "-X", "faulthandler", "-u", script, "-v"] + proc = subprocess.run(args) + print(title(f"{name} completed: exit code {proc.returncode}"), + flush=True) + if proc.returncode: + raise AssertionError(f"{name} failed") + else: + assert_python_ok("-u", script, "-v") diff --git a/Lib/test/support/socket_helper.py b/Lib/test/support/socket_helper.py index 7070c12c25..e78712b74b 100644 --- a/Lib/test/support/socket_helper.py +++ b/Lib/test/support/socket_helper.py @@ -225,7 +225,7 @@ def transient_internet(resource_name, *, timeout=_NOT_SET, errnos=()): def filter_error(err): n = getattr(err, 'errno', None) - if (isinstance(err, socket.timeout) or + if (isinstance(err, TimeoutError) or (isinstance(err, socket.gaierror) and n in gai_errnos) or (isinstance(err, urllib.error.HTTPError) and 500 <= err.code <= 599) or diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index e98c15b11a..ec9711e4f6 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -2078,7 +2078,7 @@ class TestAddSubparsers(TestCase): bar bar help {1,2,3} command help - optional arguments: + options: -h, --help show this help message and exit --foo foo help ''')) @@ -2097,7 +2097,7 @@ class TestAddSubparsers(TestCase): bar bar help {1,2,3} command help - optional arguments: + options: -h, --help show this help message and exit ++foo foo help ''')) @@ -2114,7 +2114,7 @@ class TestAddSubparsers(TestCase): main description - optional arguments: + options: -h, --help show this help message and exit --non-breaking help message containing non-breaking spaces shall not wrap\N{NO-BREAK SPACE}at non-breaking spaces @@ -2133,7 +2133,7 @@ class TestAddSubparsers(TestCase): bar bar help {1,2,3} command help - optional arguments: + options: +h, ++help show this help message and exit ++foo foo help ''')) @@ -2154,7 +2154,7 @@ class TestAddSubparsers(TestCase): 2 2 help 3 3 help - optional arguments: + options: -h, --help show this help message and exit --foo foo help ''')) @@ -2179,7 +2179,7 @@ class TestAddSubparsers(TestCase): positional arguments: bar bar help - optional arguments: + options: -h, --help show this help message and exit --foo foo help @@ -2203,7 +2203,7 @@ class TestAddSubparsers(TestCase): positional arguments: {a,b,c} x help - optional arguments: + options: -h, --help show this help message and exit -w W w help ''')) @@ -2217,7 +2217,7 @@ class TestAddSubparsers(TestCase): positional arguments: z z help - optional arguments: + options: -h, --help show this help message and exit -y {1,2,3} y help ''')) @@ -2249,7 +2249,7 @@ class TestAddSubparsers(TestCase): positional arguments: bar bar help - optional arguments: + options: -h, --help show this help message and exit --foo foo help @@ -2437,7 +2437,7 @@ class TestParentParsers(TestCase): a z - optional arguments: + options: -h, --help show this help message and exit -b B --w W @@ -2467,7 +2467,7 @@ class TestParentParsers(TestCase): self.assertEqual(parser_help, textwrap.dedent('''\ usage: {}{}[-h] [-w W] [-x X] [-y Y | -z Z] - optional arguments: + options: -h, --help show this help message and exit -y Y -z Z @@ -2512,7 +2512,7 @@ class TestMutuallyExclusiveGroupErrors(TestCase): expected = '''\ usage: PROG [-h] [--foo | --bar] [--soup | --nuts] - optional arguments: + options: -h, --help show this help message and exit --foo --bar @@ -2597,7 +2597,7 @@ class TestMutuallyExclusiveSimple(MEMixin, TestCase): ''' help = '''\ - optional arguments: + options: -h, --help show this help message and exit --bar BAR bar help --baz [BAZ] baz help @@ -2638,7 +2638,7 @@ class TestMutuallyExclusiveLong(MEMixin, TestCase): ''' help = '''\ - optional arguments: + options: -h, --help show this help message and exit --abcde ABCDE abcde help --fghij FGHIJ fghij help @@ -2674,7 +2674,7 @@ class TestMutuallyExclusiveFirstSuppressed(MEMixin, TestCase): ''' help = '''\ - optional arguments: + options: -h, --help show this help message and exit -y y help ''' @@ -2711,7 +2711,7 @@ class TestMutuallyExclusiveManySuppressed(MEMixin, TestCase): ''' help = '''\ - optional arguments: + options: -h, --help show this help message and exit ''' @@ -2754,7 +2754,7 @@ class TestMutuallyExclusiveOptionalAndPositional(MEMixin, TestCase): positional arguments: badger BADGER - optional arguments: + options: -h, --help show this help message and exit --foo FOO --spam SPAM SPAM @@ -2793,7 +2793,7 @@ class TestMutuallyExclusiveOptionalsMixed(MEMixin, TestCase): ''' help = '''\ - optional arguments: + options: -h, --help show this help message and exit -x x help -a a help @@ -2832,7 +2832,7 @@ class TestMutuallyExclusiveInGroup(MEMixin, TestCase): ''' help = '''\ - optional arguments: + options: -h, --help show this help message and exit Titled group: @@ -2877,7 +2877,7 @@ class TestMutuallyExclusiveOptionalsAndPositionalsMixed(MEMixin, TestCase): x x help a a help - optional arguments: + options: -h, --help show this help message and exit -y y help -b b help @@ -2908,7 +2908,7 @@ class TestMutuallyExclusiveNested(MEMixin, TestCase): help = '''\ - optional arguments: + options: -h, --help show this help message and exit -a A -b B @@ -3226,7 +3226,7 @@ class TestHelpBiggerOptionals(HelpTestCase): foo FOO HELP bar BAR HELP - optional arguments: + options: -h, --help show this help message and exit -v, --version show program's version number and exit -x X HELP @@ -3271,7 +3271,7 @@ class TestShortColumns(HelpTestCase): bar BAR HELP - optional arguments: + options: -h, --help show this help @@ -3321,7 +3321,7 @@ class TestHelpBiggerOptionalGroups(HelpTestCase): foo FOO HELP bar BAR HELP - optional arguments: + options: -h, --help show this help message and exit -v, --version show program's version number and exit -x X HELP @@ -3362,7 +3362,7 @@ class TestHelpBiggerPositionals(HelpTestCase): ekiekiekifekang EKI HELP bar BAR HELP - optional arguments: + options: -h, --help show this help message and exit -x X HELP --y Y Y HELP @@ -3409,7 +3409,7 @@ multiple positional arguments: yyy normal y help - optional arguments: + options: -h, --help show this help message and exit -x XX oddly formatted -x help @@ -3449,7 +3449,7 @@ DD DD DD YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YH - optional arguments: + options: -h, --help show this help message and exit -x XX XHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH \ HXXHH HXXHH @@ -3492,7 +3492,7 @@ DD DD DD YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YH - optional arguments: + options: -h, --help show this help message and exit -v, --version show program's version number and exit -x XXXXXXXXXXXXXXXXXXXXXXXXX @@ -3554,7 +3554,7 @@ class TestHelpUsage(HelpTestCase): b b c c - optional arguments: + options: -h, --help show this help message and exit -w W [W ...] w -x [X ...] x @@ -3623,7 +3623,7 @@ class TestHelpUsageLongProg(HelpTestCase): a b - optional arguments: + options: -h, --help show this help message and exit -w W -x X @@ -3657,7 +3657,7 @@ class TestHelpUsageLongProgOptionsWrap(HelpTestCase): a b - optional arguments: + options: -h, --help show this help message and exit -w WWWWWWWWWWWWWWWWWWWWWWWWW -x XXXXXXXXXXXXXXXXXXXXXXXXX @@ -3720,7 +3720,7 @@ class TestHelpUsageOptionalsWrap(HelpTestCase): b c - optional arguments: + options: -h, --help show this help message and exit -w WWWWWWWWWWWWWWWWWWWWWWWWW -x XXXXXXXXXXXXXXXXXXXXXXXXX @@ -3755,7 +3755,7 @@ class TestHelpUsagePositionalsWrap(HelpTestCase): bbbbbbbbbbbbbbbbbbbbbbbbb ccccccccccccccccccccccccc - optional arguments: + options: -h, --help show this help message and exit -x X -y Y @@ -3791,7 +3791,7 @@ class TestHelpUsageOptionalsPositionalsWrap(HelpTestCase): bbbbbbbbbbbbbbbbbbbbbbbbb ccccccccccccccccccccccccc - optional arguments: + options: -h, --help show this help message and exit -x XXXXXXXXXXXXXXXXXXXXXXXXX -y YYYYYYYYYYYYYYYYYYYYYYYYY @@ -3817,7 +3817,7 @@ class TestHelpUsageOptionalsOnlyWrap(HelpTestCase): ''' help = usage + '''\ - optional arguments: + options: -h, --help show this help message and exit -x XXXXXXXXXXXXXXXXXXXXXXXXX -y YYYYYYYYYYYYYYYYYYYYYYYYY @@ -3882,7 +3882,7 @@ class TestHelpVariableExpansion(HelpTestCase): spam spam PROG None badger badger PROG 0.5 - optional arguments: + options: -h, --help show this help message and exit -x X x PROG None int % -y y PROG 42 XXX @@ -3907,7 +3907,7 @@ class TestHelpVariableExpansionUsageSupplied(HelpTestCase): ''') help = usage + '''\ - optional arguments: + options: -h, --help show this help message and exit ''' version = '' @@ -3939,7 +3939,7 @@ class TestHelpSuppressUsage(HelpTestCase): positional arguments: spam spam help - optional arguments: + options: -h, --help show this help message and exit --foo FOO foo help ''' @@ -3986,7 +3986,7 @@ class TestHelpSuppressOptionalGroup(HelpTestCase): positional arguments: spam spam help - optional arguments: + options: -h, --help show this help message and exit --foo FOO foo help ''' @@ -4007,7 +4007,7 @@ class TestHelpSuppressPositional(HelpTestCase): ''' help = usage + '''\ - optional arguments: + options: -h, --help show this help message and exit --foo FOO foo help ''' @@ -4027,7 +4027,7 @@ class TestHelpRequiredOptional(HelpTestCase): ''' help = usage + '''\ - optional arguments: + options: -h, --help show this help message and exit --foo FOO foo help ''' @@ -4048,7 +4048,7 @@ class TestHelpAlternatePrefixChars(HelpTestCase): ''' help = usage + '''\ - optional arguments: + options: ^^foo foo help ;b BAR, ;;bar BAR bar help ''' @@ -4072,7 +4072,7 @@ class TestHelpNoHelpOptional(HelpTestCase): positional arguments: spam spam help - optional arguments: + options: --foo FOO foo help ''' version = '' @@ -4095,7 +4095,7 @@ class TestHelpNone(HelpTestCase): positional arguments: spam - optional arguments: + options: -h, --help show this help message and exit --foo FOO ''' @@ -4119,7 +4119,7 @@ class TestHelpTupleMetavar(HelpTestCase): ''' help = usage + '''\ - optional arguments: + options: -h, --help show this help message and exit -w W1 [W2 ...] w -x [X1 [X2 ...]] x @@ -4163,7 +4163,7 @@ class TestHelpRawText(HelpTestCase): positional arguments: spam spam help - optional arguments: + options: -h, --help show this help message and exit --foo FOO foo help should also appear as given here @@ -4212,7 +4212,7 @@ class TestHelpRawDescription(HelpTestCase): positional arguments: spam spam help - optional arguments: + options: -h, --help show this help message and exit --foo FOO foo help should not retain this odd formatting @@ -4254,7 +4254,7 @@ class TestHelpArgumentDefaults(HelpTestCase): spam spam help badger badger help (default: wooden) - optional arguments: + options: -h, --help show this help message and exit --foo FOO foo help - oh and by the way, None --bar bar help (default: False) @@ -4279,7 +4279,7 @@ class TestHelpVersionAction(HelpTestCase): description - optional arguments: + options: -h, --help show this help message and exit -V, --version show program's version number and exit ''' @@ -4305,7 +4305,7 @@ class TestHelpVersionActionSuppress(HelpTestCase): positional arguments: spam spam help - optional arguments: + options: -h, --help show this help message and exit --foo FOO foo help ''' @@ -4331,7 +4331,7 @@ class TestHelpSubparsersOrdering(HelpTestCase): positional arguments: {a,b,c,d,e} - optional arguments: + options: -h, --help show this help message and exit -v, --version show program's version number and exit ''' @@ -4372,7 +4372,7 @@ class TestHelpSubparsersWithHelpOrdering(HelpTestCase): d d subcommand help e e subcommand help - optional arguments: + options: -h, --help show this help message and exit -v, --version show program's version number and exit ''' @@ -4404,7 +4404,7 @@ class TestHelpMetavarTypeFormatter(HelpTestCase): positional arguments: int - optional arguments: + options: -h, --help show this help message and exit -b custom_type -c SOME FLOAT @@ -4596,7 +4596,7 @@ class TestConflictHandling(TestCase): self.assertEqual(parser.format_help(), textwrap.dedent('''\ usage: PROG [-h] [-x X] - optional arguments: + options: -h, --help show this help message and exit -x X NEW X ''')) @@ -4606,7 +4606,7 @@ class TestConflictHandling(TestCase): self.assertEqual(parser.format_help(), textwrap.dedent('''\ usage: PROG [-h] [-x X] [--spam NEW_SPAM] - optional arguments: + options: -h, --help show this help message and exit -x X NEW X --spam NEW_SPAM @@ -5337,7 +5337,7 @@ class TestWrappingMetavar(TestCase): usage: this_is_spammy_prog_with_a_long_name_sorry_about_the_name [-h] [--proxy <http[s]://example:1234>] - optional arguments: + options: -h, --help show this help message and exit --proxy <http[s]://example:1234> ''')) diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index c423f54548..77a0c64a88 100644 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -393,7 +393,7 @@ class BaseTest: a.insert(0, self.outside) self.assertEqual(list(exhit), []) # The iterator index points past the 0th position so inserting - # an element in the beggining does not make it appear. + # an element in the beginning does not make it appear. self.assertEqual(list(empit), []) self.assertEqual(list(a), [self.outside] + list(self.example)) diff --git a/Lib/test/test_asdl_parser.py b/Lib/test/test_asdl_parser.py index 2c14817aae..d2c2b51334 100644 --- a/Lib/test/test_asdl_parser.py +++ b/Lib/test/test_asdl_parser.py @@ -1,6 +1,7 @@ """Tests for the asdl parser in Parser/asdl.py""" import importlib.machinery +import importlib.util import os from os.path import dirname import sys @@ -26,7 +27,10 @@ class TestAsdlParser(unittest.TestCase): sys.path.insert(0, parser_dir) loader = importlib.machinery.SourceFileLoader( 'asdl', os.path.join(parser_dir, 'asdl.py')) - cls.asdl = loader.load_module() + spec = importlib.util.spec_from_loader('asdl', loader) + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) + cls.asdl = module cls.mod = cls.asdl.parse(os.path.join(parser_dir, 'Python.asdl')) cls.assertTrue(cls.asdl.check(cls.mod), 'Module validation failed') diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index be4b0f78ce..451f40d1f8 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1011,6 +1011,18 @@ Module( self.assertEqual(ast.literal_eval(" \t -1"), -1) self.assertRaises(IndentationError, ast.literal_eval, "\n -1") + def test_literal_eval_malformed_lineno(self): + msg = r'malformed node or string on line 3:' + with self.assertRaisesRegex(ValueError, msg): + ast.literal_eval("{'a': 1,\n'b':2,\n'c':++3,\n'd':4}") + + node = ast.UnaryOp( + ast.UAdd(), ast.UnaryOp(ast.UAdd(), ast.Constant(6))) + self.assertIsNone(getattr(node, 'lineno', None)) + msg = r'malformed node or string:' + with self.assertRaisesRegex(ValueError, msg): + ast.literal_eval(node) + def test_bad_integer(self): # issue13436: Bad error message with invalid numeric values body = [ast.ImportFrom(module='time', diff --git a/Lib/test/test_asyncio/functional.py b/Lib/test/test_asyncio/functional.py index 5cd0659387..74490a869d 100644 --- a/Lib/test/test_asyncio/functional.py +++ b/Lib/test/test_asyncio/functional.py @@ -248,7 +248,7 @@ class TestThreadedServer(SocketThread): conn, addr = self._sock.accept() except BlockingIOError: continue - except socket.timeout: + except TimeoutError: if not self._active: return else: diff --git a/Lib/test/test_asyncio/test_asyncio_waitfor.py b/Lib/test/test_asyncio/test_asyncio_waitfor.py new file mode 100644 index 0000000000..2ca64abbeb --- /dev/null +++ b/Lib/test/test_asyncio/test_asyncio_waitfor.py @@ -0,0 +1,61 @@ +import asyncio +import unittest +import time + +def tearDownModule(): + asyncio.set_event_loop_policy(None) + + +class SlowTask: + """ Task will run for this defined time, ignoring cancel requests """ + TASK_TIMEOUT = 0.2 + + def __init__(self): + self.exited = False + + async def run(self): + exitat = time.monotonic() + self.TASK_TIMEOUT + + while True: + tosleep = exitat - time.monotonic() + if tosleep <= 0: + break + + try: + await asyncio.sleep(tosleep) + except asyncio.CancelledError: + pass + + self.exited = True + +class AsyncioWaitForTest(unittest.TestCase): + + async def atest_asyncio_wait_for_cancelled(self): + t = SlowTask() + + waitfortask = asyncio.create_task(asyncio.wait_for(t.run(), t.TASK_TIMEOUT * 2)) + await asyncio.sleep(0) + waitfortask.cancel() + await asyncio.wait({waitfortask}) + + self.assertTrue(t.exited) + + def test_asyncio_wait_for_cancelled(self): + asyncio.run(self.atest_asyncio_wait_for_cancelled()) + + async def atest_asyncio_wait_for_timeout(self): + t = SlowTask() + + try: + await asyncio.wait_for(t.run(), t.TASK_TIMEOUT / 2) + except asyncio.TimeoutError: + pass + + self.assertTrue(t.exited) + + def test_asyncio_wait_for_timeout(self): + asyncio.run(self.atest_asyncio_wait_for_timeout()) + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index f74dabc2db..e40e7999b6 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1160,9 +1160,7 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase): @unittest.skipUnless(socket_helper.IPV6_ENABLED, 'no IPv6 support') def test_create_server_ipv6(self): async def main(): - with self.assertWarns(DeprecationWarning): - srv = await asyncio.start_server( - lambda: None, '::1', 0, loop=self.loop) + srv = await asyncio.start_server(lambda: None, '::1', 0) try: self.assertGreater(len(srv.sockets), 0) finally: @@ -1747,6 +1745,8 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase): MyDatagramProto, allow_broadcast=True, sock=FakeSock()) self.assertRaises(ValueError, self.loop.run_until_complete, fut) + @unittest.skipIf(sys.platform == 'vxworks', + "SO_BROADCAST is enabled by default on VxWorks") def test_create_datagram_endpoint_sockopts(self): # Socket options should not be applied unless asked for. # SO_REUSEPORT is not available on all platforms. diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index b8fe466cd5..6523a79b4a 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -22,7 +22,7 @@ import unittest from unittest import mock import weakref -if sys.platform != 'win32': +if sys.platform not in ('win32', 'vxworks'): import tty import asyncio @@ -206,8 +206,8 @@ class MySubprocessProtocol(asyncio.SubprocessProtocol): self.disconnects = {fd: loop.create_future() for fd in range(3)} self.data = {1: b'', 2: b''} self.returncode = None - self.got_data = {1: asyncio.Event(loop=loop), - 2: asyncio.Event(loop=loop)} + self.got_data = {1: asyncio.Event(), + 2: asyncio.Event()} def connection_made(self, transport): self.transport = transport @@ -294,11 +294,8 @@ class EventLoopTestsMixin: self.loop.stop() self.loop.call_later(0.1, callback, 'hello world') - t0 = time.monotonic() self.loop.run_forever() - t1 = time.monotonic() self.assertEqual(results, ['hello world']) - self.assertTrue(0.08 <= t1-t0 <= 0.8, t1-t0) def test_call_soon(self): results = [] @@ -468,6 +465,8 @@ class EventLoopTestsMixin: self.assertFalse(self.loop.remove_signal_handler(signal.SIGINT)) @unittest.skipUnless(hasattr(signal, 'SIGALRM'), 'No SIGALRM') + @unittest.skipUnless(hasattr(signal, 'setitimer'), + 'need signal.setitimer()') def test_signal_handling_while_selecting(self): # Test with a signal actually arriving during a select() call. caught = 0 @@ -485,6 +484,8 @@ class EventLoopTestsMixin: self.assertEqual(caught, 1) @unittest.skipUnless(hasattr(signal, 'SIGALRM'), 'No SIGALRM') + @unittest.skipUnless(hasattr(signal, 'setitimer'), + 'need signal.setitimer()') def test_signal_handling_args(self): some_args = (42,) caught = 0 @@ -1374,6 +1375,7 @@ class EventLoopTestsMixin: @unittest.skipUnless(sys.platform != 'win32', "Don't support pipes for Windows") + @unittest.skipUnless(hasattr(os, 'openpty'), 'need os.openpty()') def test_read_pty_output(self): proto = MyReadPipeProto(loop=self.loop) @@ -1471,6 +1473,7 @@ class EventLoopTestsMixin: @unittest.skipUnless(sys.platform != 'win32', "Don't support pipes for Windows") + @unittest.skipUnless(hasattr(os, 'openpty'), 'need os.openpty()') # select, poll and kqueue don't support character devices (PTY) on Mac OS X # older than 10.6 (Snow Leopard) @support.requires_mac_ver(10, 6) @@ -1515,6 +1518,7 @@ class EventLoopTestsMixin: @unittest.skipUnless(sys.platform != 'win32', "Don't support pipes for Windows") + @unittest.skipUnless(hasattr(os, 'openpty'), 'need os.openpty()') # select, poll and kqueue don't support character devices (PTY) on Mac OS X # older than 10.6 (Snow Leopard) @support.requires_mac_ver(10, 6) @@ -1740,20 +1744,20 @@ class SubprocessTestsMixin: connect = self.loop.subprocess_exec( functools.partial(MySubprocessProtocol, self.loop), sys.executable, prog) - with self.assertWarns(DeprecationWarning): - transp, proto = self.loop.run_until_complete(connect) - self.assertIsInstance(proto, MySubprocessProtocol) - self.loop.run_until_complete(proto.connected) - self.assertEqual('CONNECTED', proto.state) - stdin = transp.get_pipe_transport(0) - stdin.write(b'Python The Winner') - self.loop.run_until_complete(proto.got_data[1].wait()) - with test_utils.disable_logger(): - transp.close() - self.loop.run_until_complete(proto.completed) - self.check_killed(proto.returncode) - self.assertEqual(b'Python The Winner', proto.data[1]) + transp, proto = self.loop.run_until_complete(connect) + self.assertIsInstance(proto, MySubprocessProtocol) + self.loop.run_until_complete(proto.connected) + self.assertEqual('CONNECTED', proto.state) + + stdin = transp.get_pipe_transport(0) + stdin.write(b'Python The Winner') + self.loop.run_until_complete(proto.got_data[1].wait()) + with test_utils.disable_logger(): + transp.close() + self.loop.run_until_complete(proto.completed) + self.check_killed(proto.returncode) + self.assertEqual(b'Python The Winner', proto.data[1]) def test_subprocess_interactive(self): prog = os.path.join(os.path.dirname(__file__), 'echo.py') @@ -1762,51 +1766,48 @@ class SubprocessTestsMixin: functools.partial(MySubprocessProtocol, self.loop), sys.executable, prog) - with self.assertWarns(DeprecationWarning): - transp, proto = self.loop.run_until_complete(connect) - self.assertIsInstance(proto, MySubprocessProtocol) - self.loop.run_until_complete(proto.connected) - self.assertEqual('CONNECTED', proto.state) + transp, proto = self.loop.run_until_complete(connect) + self.assertIsInstance(proto, MySubprocessProtocol) + self.loop.run_until_complete(proto.connected) + self.assertEqual('CONNECTED', proto.state) - stdin = transp.get_pipe_transport(0) - stdin.write(b'Python ') - self.loop.run_until_complete(proto.got_data[1].wait()) - proto.got_data[1].clear() - self.assertEqual(b'Python ', proto.data[1]) + stdin = transp.get_pipe_transport(0) + stdin.write(b'Python ') + self.loop.run_until_complete(proto.got_data[1].wait()) + proto.got_data[1].clear() + self.assertEqual(b'Python ', proto.data[1]) - stdin.write(b'The Winner') - self.loop.run_until_complete(proto.got_data[1].wait()) - self.assertEqual(b'Python The Winner', proto.data[1]) + stdin.write(b'The Winner') + self.loop.run_until_complete(proto.got_data[1].wait()) + self.assertEqual(b'Python The Winner', proto.data[1]) - with test_utils.disable_logger(): - transp.close() - self.loop.run_until_complete(proto.completed) - self.check_killed(proto.returncode) + with test_utils.disable_logger(): + transp.close() + self.loop.run_until_complete(proto.completed) + self.check_killed(proto.returncode) def test_subprocess_shell(self): - with self.assertWarns(DeprecationWarning): - connect = self.loop.subprocess_shell( - functools.partial(MySubprocessProtocol, self.loop), - 'echo Python') - transp, proto = self.loop.run_until_complete(connect) - self.assertIsInstance(proto, MySubprocessProtocol) - self.loop.run_until_complete(proto.connected) + connect = self.loop.subprocess_shell( + functools.partial(MySubprocessProtocol, self.loop), + 'echo Python') + transp, proto = self.loop.run_until_complete(connect) + self.assertIsInstance(proto, MySubprocessProtocol) + self.loop.run_until_complete(proto.connected) - transp.get_pipe_transport(0).close() - self.loop.run_until_complete(proto.completed) - self.assertEqual(0, proto.returncode) - self.assertTrue(all(f.done() for f in proto.disconnects.values())) - self.assertEqual(proto.data[1].rstrip(b'\r\n'), b'Python') - self.assertEqual(proto.data[2], b'') - transp.close() + transp.get_pipe_transport(0).close() + self.loop.run_until_complete(proto.completed) + self.assertEqual(0, proto.returncode) + self.assertTrue(all(f.done() for f in proto.disconnects.values())) + self.assertEqual(proto.data[1].rstrip(b'\r\n'), b'Python') + self.assertEqual(proto.data[2], b'') + transp.close() def test_subprocess_exitcode(self): connect = self.loop.subprocess_shell( functools.partial(MySubprocessProtocol, self.loop), 'exit 7', stdin=None, stdout=None, stderr=None) - with self.assertWarns(DeprecationWarning): - transp, proto = self.loop.run_until_complete(connect) + transp, proto = self.loop.run_until_complete(connect) self.assertIsInstance(proto, MySubprocessProtocol) self.loop.run_until_complete(proto.completed) self.assertEqual(7, proto.returncode) @@ -1816,8 +1817,8 @@ class SubprocessTestsMixin: connect = self.loop.subprocess_shell( functools.partial(MySubprocessProtocol, self.loop), 'exit 7', stdin=None, stdout=None, stderr=None) - with self.assertWarns(DeprecationWarning): - transp, proto = self.loop.run_until_complete(connect) + + transp, proto = self.loop.run_until_complete(connect) self.assertIsInstance(proto, MySubprocessProtocol) self.assertIsNone(transp.get_pipe_transport(0)) self.assertIsNone(transp.get_pipe_transport(1)) @@ -1833,15 +1834,14 @@ class SubprocessTestsMixin: functools.partial(MySubprocessProtocol, self.loop), sys.executable, prog) - with self.assertWarns(DeprecationWarning): - transp, proto = self.loop.run_until_complete(connect) - self.assertIsInstance(proto, MySubprocessProtocol) - self.loop.run_until_complete(proto.connected) + transp, proto = self.loop.run_until_complete(connect) + self.assertIsInstance(proto, MySubprocessProtocol) + self.loop.run_until_complete(proto.connected) - transp.kill() - self.loop.run_until_complete(proto.completed) - self.check_killed(proto.returncode) - transp.close() + transp.kill() + self.loop.run_until_complete(proto.completed) + self.check_killed(proto.returncode) + transp.close() def test_subprocess_terminate(self): prog = os.path.join(os.path.dirname(__file__), 'echo.py') @@ -1850,15 +1850,14 @@ class SubprocessTestsMixin: functools.partial(MySubprocessProtocol, self.loop), sys.executable, prog) - with self.assertWarns(DeprecationWarning): - transp, proto = self.loop.run_until_complete(connect) - self.assertIsInstance(proto, MySubprocessProtocol) - self.loop.run_until_complete(proto.connected) + transp, proto = self.loop.run_until_complete(connect) + self.assertIsInstance(proto, MySubprocessProtocol) + self.loop.run_until_complete(proto.connected) - transp.terminate() - self.loop.run_until_complete(proto.completed) - self.check_terminated(proto.returncode) - transp.close() + transp.terminate() + self.loop.run_until_complete(proto.completed) + self.check_terminated(proto.returncode) + transp.close() @unittest.skipIf(sys.platform == 'win32', "Don't have SIGHUP") def test_subprocess_send_signal(self): @@ -1873,15 +1872,15 @@ class SubprocessTestsMixin: functools.partial(MySubprocessProtocol, self.loop), sys.executable, prog) - with self.assertWarns(DeprecationWarning): - transp, proto = self.loop.run_until_complete(connect) - self.assertIsInstance(proto, MySubprocessProtocol) - self.loop.run_until_complete(proto.connected) - transp.send_signal(signal.SIGHUP) - self.loop.run_until_complete(proto.completed) - self.assertEqual(-signal.SIGHUP, proto.returncode) - transp.close() + transp, proto = self.loop.run_until_complete(connect) + self.assertIsInstance(proto, MySubprocessProtocol) + self.loop.run_until_complete(proto.connected) + + transp.send_signal(signal.SIGHUP) + self.loop.run_until_complete(proto.completed) + self.assertEqual(-signal.SIGHUP, proto.returncode) + transp.close() finally: signal.signal(signal.SIGHUP, old_handler) @@ -1892,20 +1891,19 @@ class SubprocessTestsMixin: functools.partial(MySubprocessProtocol, self.loop), sys.executable, prog) - with self.assertWarns(DeprecationWarning): - transp, proto = self.loop.run_until_complete(connect) - self.assertIsInstance(proto, MySubprocessProtocol) - self.loop.run_until_complete(proto.connected) + transp, proto = self.loop.run_until_complete(connect) + self.assertIsInstance(proto, MySubprocessProtocol) + self.loop.run_until_complete(proto.connected) - stdin = transp.get_pipe_transport(0) - stdin.write(b'test') + stdin = transp.get_pipe_transport(0) + stdin.write(b'test') - self.loop.run_until_complete(proto.completed) + self.loop.run_until_complete(proto.completed) - transp.close() - self.assertEqual(b'OUT:test', proto.data[1]) - self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2]) - self.assertEqual(0, proto.returncode) + transp.close() + self.assertEqual(b'OUT:test', proto.data[1]) + self.assertTrue(proto.data[2].startswith(b'ERR:test'), proto.data[2]) + self.assertEqual(0, proto.returncode) def test_subprocess_stderr_redirect_to_stdout(self): prog = os.path.join(os.path.dirname(__file__), 'echo2.py') @@ -1914,23 +1912,23 @@ class SubprocessTestsMixin: functools.partial(MySubprocessProtocol, self.loop), sys.executable, prog, stderr=subprocess.STDOUT) - with self.assertWarns(DeprecationWarning): - transp, proto = self.loop.run_until_complete(connect) - self.assertIsInstance(proto, MySubprocessProtocol) - self.loop.run_until_complete(proto.connected) - stdin = transp.get_pipe_transport(0) - self.assertIsNotNone(transp.get_pipe_transport(1)) - self.assertIsNone(transp.get_pipe_transport(2)) + transp, proto = self.loop.run_until_complete(connect) + self.assertIsInstance(proto, MySubprocessProtocol) + self.loop.run_until_complete(proto.connected) - stdin.write(b'test') - self.loop.run_until_complete(proto.completed) - self.assertTrue(proto.data[1].startswith(b'OUT:testERR:test'), - proto.data[1]) - self.assertEqual(b'', proto.data[2]) + stdin = transp.get_pipe_transport(0) + self.assertIsNotNone(transp.get_pipe_transport(1)) + self.assertIsNone(transp.get_pipe_transport(2)) - transp.close() - self.assertEqual(0, proto.returncode) + stdin.write(b'test') + self.loop.run_until_complete(proto.completed) + self.assertTrue(proto.data[1].startswith(b'OUT:testERR:test'), + proto.data[1]) + self.assertEqual(b'', proto.data[2]) + + transp.close() + self.assertEqual(0, proto.returncode) def test_subprocess_close_client_stream(self): prog = os.path.join(os.path.dirname(__file__), 'echo3.py') @@ -1938,33 +1936,33 @@ class SubprocessTestsMixin: connect = self.loop.subprocess_exec( functools.partial(MySubprocessProtocol, self.loop), sys.executable, prog) - with self.assertWarns(DeprecationWarning): - transp, proto = self.loop.run_until_complete(connect) - self.assertIsInstance(proto, MySubprocessProtocol) - self.loop.run_until_complete(proto.connected) - stdin = transp.get_pipe_transport(0) - stdout = transp.get_pipe_transport(1) - stdin.write(b'test') - self.loop.run_until_complete(proto.got_data[1].wait()) - self.assertEqual(b'OUT:test', proto.data[1]) + transp, proto = self.loop.run_until_complete(connect) + self.assertIsInstance(proto, MySubprocessProtocol) + self.loop.run_until_complete(proto.connected) - stdout.close() - self.loop.run_until_complete(proto.disconnects[1]) - stdin.write(b'xxx') - self.loop.run_until_complete(proto.got_data[2].wait()) - if sys.platform != 'win32': - self.assertEqual(b'ERR:BrokenPipeError', proto.data[2]) - else: - # After closing the read-end of a pipe, writing to the - # write-end using os.write() fails with errno==EINVAL and - # GetLastError()==ERROR_INVALID_NAME on Windows!?! (Using - # WriteFile() we get ERROR_BROKEN_PIPE as expected.) - self.assertEqual(b'ERR:OSError', proto.data[2]) - with test_utils.disable_logger(): - transp.close() - self.loop.run_until_complete(proto.completed) - self.check_killed(proto.returncode) + stdin = transp.get_pipe_transport(0) + stdout = transp.get_pipe_transport(1) + stdin.write(b'test') + self.loop.run_until_complete(proto.got_data[1].wait()) + self.assertEqual(b'OUT:test', proto.data[1]) + + stdout.close() + self.loop.run_until_complete(proto.disconnects[1]) + stdin.write(b'xxx') + self.loop.run_until_complete(proto.got_data[2].wait()) + if sys.platform != 'win32': + self.assertEqual(b'ERR:BrokenPipeError', proto.data[2]) + else: + # After closing the read-end of a pipe, writing to the + # write-end using os.write() fails with errno==EINVAL and + # GetLastError()==ERROR_INVALID_NAME on Windows!?! (Using + # WriteFile() we get ERROR_BROKEN_PIPE as expected.) + self.assertEqual(b'ERR:OSError', proto.data[2]) + with test_utils.disable_logger(): + transp.close() + self.loop.run_until_complete(proto.completed) + self.check_killed(proto.returncode) def test_subprocess_wait_no_same_group(self): # start the new process in a new session diff --git a/Lib/test/test_asyncio/test_futures2.py b/Lib/test/test_asyncio/test_futures2.py new file mode 100644 index 0000000000..13dbc70327 --- /dev/null +++ b/Lib/test/test_asyncio/test_futures2.py @@ -0,0 +1,18 @@ +# IsolatedAsyncioTestCase based tests +import asyncio +import unittest + + +class FutureTests(unittest.IsolatedAsyncioTestCase): + async def test_recursive_repr_for_pending_tasks(self): + # The call crashes if the guard for recursive call + # in base_futures:_future_repr_info is absent + # See Also: https://bugs.python.org/issue42183 + + async def func(): + return asyncio.all_tasks() + + # The repr() call should not raise RecursiveError at first. + # The check for returned string is not very reliable but + # exact comparison for the whole string is even weaker. + self.assertIn('...', repr(await asyncio.wait_for(func(), timeout=10))) diff --git a/Lib/test/test_asyncio/test_locks.py b/Lib/test/test_asyncio/test_locks.py index 8c93fae2b5..6194cd0617 100644 --- a/Lib/test/test_asyncio/test_locks.py +++ b/Lib/test/test_asyncio/test_locks.py @@ -26,24 +26,8 @@ class LockTests(test_utils.TestCase): super().setUp() self.loop = self.new_test_loop() - def test_ctor_loop(self): - loop = mock.Mock() - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=loop) - self.assertIs(lock._loop, loop) - - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) - self.assertIs(lock._loop, self.loop) - - def test_ctor_noloop(self): - asyncio.set_event_loop(self.loop) - lock = asyncio.Lock() - self.assertIs(lock._loop, self.loop) - def test_repr(self): - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) + lock = asyncio.Lock() self.assertTrue(repr(lock).endswith('[unlocked]>')) self.assertTrue(RGX_REPR.match(repr(lock))) @@ -52,9 +36,9 @@ class LockTests(test_utils.TestCase): self.assertTrue(RGX_REPR.match(repr(lock))) def test_lock(self): - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) + lock = asyncio.Lock() + with self.assertWarns(DeprecationWarning): @asyncio.coroutine def acquire_lock(): return (yield from lock) @@ -67,17 +51,34 @@ class LockTests(test_utils.TestCase): self.assertFalse(lock.locked()) + def test_lock_doesnt_accept_loop_parameter(self): + primitives_cls = [ + asyncio.Lock, + asyncio.Condition, + asyncio.Event, + asyncio.Semaphore, + asyncio.BoundedSemaphore, + ] + + for cls in primitives_cls: + with self.assertRaisesRegex( + TypeError, + rf'As of 3.10, the \*loop\* parameter was removed from ' + rf'{cls.__name__}\(\) since it is no longer necessary' + ): + cls(loop=self.loop) + def test_lock_by_with_statement(self): loop = asyncio.new_event_loop() # don't use TestLoop quirks self.set_event_loop(loop) - with self.assertWarns(DeprecationWarning): - primitives = [ - asyncio.Lock(loop=loop), - asyncio.Condition(loop=loop), - asyncio.Semaphore(loop=loop), - asyncio.BoundedSemaphore(loop=loop), - ] + primitives = [ + asyncio.Lock(), + asyncio.Condition(), + asyncio.Semaphore(), + asyncio.BoundedSemaphore(), + ] + with self.assertWarns(DeprecationWarning): @asyncio.coroutine def test(lock): yield from asyncio.sleep(0.01) @@ -95,8 +96,7 @@ class LockTests(test_utils.TestCase): self.assertFalse(primitive.locked()) def test_acquire(self): - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) + lock = asyncio.Lock() result = [] self.assertTrue(self.loop.run_until_complete(lock.acquire())) @@ -147,8 +147,7 @@ class LockTests(test_utils.TestCase): self.assertTrue(t3.result()) def test_acquire_cancel(self): - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) + lock = asyncio.Lock() self.assertTrue(self.loop.run_until_complete(lock.acquire())) task = self.loop.create_task(lock.acquire()) @@ -173,8 +172,7 @@ class LockTests(test_utils.TestCase): # B's waiter; instead, it should move on to C's waiter. # Setup: A has the lock, b and c are waiting. - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) + lock = asyncio.Lock() async def lockit(name, blocker): await lock.acquire() @@ -210,8 +208,7 @@ class LockTests(test_utils.TestCase): # Issue 32734 # Acquire 4 locks, cancel second, release first # and 2 locks are taken at once. - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) + lock = asyncio.Lock() lock_count = 0 call_count = 0 @@ -256,8 +253,7 @@ class LockTests(test_utils.TestCase): self.assertTrue(t3.cancelled()) def test_finished_waiter_cancelled(self): - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) + lock = asyncio.Lock() ta = self.loop.create_task(lock.acquire()) test_utils.run_briefly(self.loop) @@ -279,14 +275,12 @@ class LockTests(test_utils.TestCase): self.assertTrue(tb.cancelled()) def test_release_not_acquired(self): - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) + lock = asyncio.Lock() self.assertRaises(RuntimeError, lock.release) def test_release_no_waiters(self): - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) + lock = asyncio.Lock() self.loop.run_until_complete(lock.acquire()) self.assertTrue(lock.locked()) @@ -312,24 +306,8 @@ class EventTests(test_utils.TestCase): super().setUp() self.loop = self.new_test_loop() - def test_ctor_loop(self): - loop = mock.Mock() - with self.assertWarns(DeprecationWarning): - ev = asyncio.Event(loop=loop) - self.assertIs(ev._loop, loop) - - with self.assertWarns(DeprecationWarning): - ev = asyncio.Event(loop=self.loop) - self.assertIs(ev._loop, self.loop) - - def test_ctor_noloop(self): - asyncio.set_event_loop(self.loop) - ev = asyncio.Event() - self.assertIs(ev._loop, self.loop) - def test_repr(self): - with self.assertWarns(DeprecationWarning): - ev = asyncio.Event(loop=self.loop) + ev = asyncio.Event() self.assertTrue(repr(ev).endswith('[unset]>')) match = RGX_REPR.match(repr(ev)) self.assertEqual(match.group('extras'), 'unset') @@ -343,8 +321,7 @@ class EventTests(test_utils.TestCase): self.assertTrue(RGX_REPR.match(repr(ev))) def test_wait(self): - with self.assertWarns(DeprecationWarning): - ev = asyncio.Event(loop=self.loop) + ev = asyncio.Event() self.assertFalse(ev.is_set()) result = [] @@ -381,16 +358,14 @@ class EventTests(test_utils.TestCase): self.assertIsNone(t3.result()) def test_wait_on_set(self): - with self.assertWarns(DeprecationWarning): - ev = asyncio.Event(loop=self.loop) + ev = asyncio.Event() ev.set() res = self.loop.run_until_complete(ev.wait()) self.assertTrue(res) def test_wait_cancel(self): - with self.assertWarns(DeprecationWarning): - ev = asyncio.Event(loop=self.loop) + ev = asyncio.Event() wait = self.loop.create_task(ev.wait()) self.loop.call_soon(wait.cancel) @@ -400,8 +375,7 @@ class EventTests(test_utils.TestCase): self.assertFalse(ev._waiters) def test_clear(self): - with self.assertWarns(DeprecationWarning): - ev = asyncio.Event(loop=self.loop) + ev = asyncio.Event() self.assertFalse(ev.is_set()) ev.set() @@ -411,8 +385,7 @@ class EventTests(test_utils.TestCase): self.assertFalse(ev.is_set()) def test_clear_with_waiters(self): - with self.assertWarns(DeprecationWarning): - ev = asyncio.Event(loop=self.loop) + ev = asyncio.Event() result = [] async def c1(result): @@ -446,23 +419,8 @@ class ConditionTests(test_utils.TestCase): super().setUp() self.loop = self.new_test_loop() - def test_ctor_loop(self): - loop = mock.Mock() - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=loop) - self.assertIs(cond._loop, loop) - - cond = asyncio.Condition(loop=self.loop) - self.assertIs(cond._loop, self.loop) - - def test_ctor_noloop(self): - asyncio.set_event_loop(self.loop) - cond = asyncio.Condition() - self.assertIs(cond._loop, self.loop) - def test_wait(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) + cond = asyncio.Condition() result = [] async def c1(result): @@ -525,8 +483,7 @@ class ConditionTests(test_utils.TestCase): self.assertTrue(t3.result()) def test_wait_cancel(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) + cond = asyncio.Condition() self.loop.run_until_complete(cond.acquire()) wait = self.loop.create_task(cond.wait()) @@ -538,8 +495,7 @@ class ConditionTests(test_utils.TestCase): self.assertTrue(cond.locked()) def test_wait_cancel_contested(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) + cond = asyncio.Condition() self.loop.run_until_complete(cond.acquire()) self.assertTrue(cond.locked()) @@ -565,10 +521,11 @@ class ConditionTests(test_utils.TestCase): def test_wait_cancel_after_notify(self): # See bpo-32841 - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) waited = False + cond = asyncio.Condition() + cond._loop = self.loop + async def wait_on_cond(): nonlocal waited async with cond: @@ -590,15 +547,13 @@ class ConditionTests(test_utils.TestCase): self.assertTrue(waited) def test_wait_unacquired(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) + cond = asyncio.Condition() self.assertRaises( RuntimeError, self.loop.run_until_complete, cond.wait()) def test_wait_for(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) + cond = asyncio.Condition() presult = False def predicate(): @@ -635,8 +590,7 @@ class ConditionTests(test_utils.TestCase): self.assertTrue(t.result()) def test_wait_for_unacquired(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) + cond = asyncio.Condition() # predicate can return true immediately res = self.loop.run_until_complete(cond.wait_for(lambda: [1, 2, 3])) @@ -648,8 +602,7 @@ class ConditionTests(test_utils.TestCase): cond.wait_for(lambda: False)) def test_notify(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) + cond = asyncio.Condition() result = [] async def c1(result): @@ -701,8 +654,7 @@ class ConditionTests(test_utils.TestCase): self.assertTrue(t3.result()) def test_notify_all(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) + cond = asyncio.Condition() result = [] @@ -738,18 +690,15 @@ class ConditionTests(test_utils.TestCase): self.assertTrue(t2.result()) def test_notify_unacquired(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) + cond = asyncio.Condition() self.assertRaises(RuntimeError, cond.notify) def test_notify_all_unacquired(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) + cond = asyncio.Condition() self.assertRaises(RuntimeError, cond.notify_all) def test_repr(self): - with self.assertWarns(DeprecationWarning): - cond = asyncio.Condition(loop=self.loop) + cond = asyncio.Condition() self.assertTrue('unlocked' in repr(cond)) self.assertTrue(RGX_REPR.match(repr(cond))) @@ -775,9 +724,8 @@ class ConditionTests(test_utils.TestCase): self.loop.run_until_complete(f()) def test_explicit_lock(self): - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) - cond = asyncio.Condition(lock, loop=self.loop) + lock = asyncio.Lock() + cond = asyncio.Condition(lock) self.assertIs(cond._lock, lock) self.assertIs(cond._loop, lock._loop) @@ -785,23 +733,27 @@ class ConditionTests(test_utils.TestCase): def test_ambiguous_loops(self): loop = self.new_test_loop() self.addCleanup(loop.close) - with self.assertWarns(DeprecationWarning): - lock = asyncio.Lock(loop=self.loop) + + lock = asyncio.Lock() + lock._loop = loop + + async def _create_condition(): with self.assertRaises(ValueError): - asyncio.Condition(lock, loop=loop) + asyncio.Condition(lock) + + self.loop.run_until_complete(_create_condition()) def test_timeout_in_block(self): loop = asyncio.new_event_loop() self.addCleanup(loop.close) async def task_timeout(): - condition = asyncio.Condition(loop=loop) + condition = asyncio.Condition() async with condition: with self.assertRaises(asyncio.TimeoutError): await asyncio.wait_for(condition.wait(), timeout=0.5) - with self.assertWarns(DeprecationWarning): - loop.run_until_complete(task_timeout()) + loop.run_until_complete(task_timeout()) class SemaphoreTests(test_utils.TestCase): @@ -810,29 +762,12 @@ class SemaphoreTests(test_utils.TestCase): super().setUp() self.loop = self.new_test_loop() - def test_ctor_loop(self): - loop = mock.Mock() - with self.assertWarns(DeprecationWarning): - sem = asyncio.Semaphore(loop=loop) - self.assertIs(sem._loop, loop) - - with self.assertWarns(DeprecationWarning): - sem = asyncio.Semaphore(loop=self.loop) - self.assertIs(sem._loop, self.loop) - - def test_ctor_noloop(self): - asyncio.set_event_loop(self.loop) - sem = asyncio.Semaphore() - self.assertIs(sem._loop, self.loop) - def test_initial_value_zero(self): - with self.assertWarns(DeprecationWarning): - sem = asyncio.Semaphore(0, loop=self.loop) + sem = asyncio.Semaphore(0) self.assertTrue(sem.locked()) def test_repr(self): - with self.assertWarns(DeprecationWarning): - sem = asyncio.Semaphore(loop=self.loop) + sem = asyncio.Semaphore() self.assertTrue(repr(sem).endswith('[unlocked, value:1]>')) self.assertTrue(RGX_REPR.match(repr(sem))) @@ -850,8 +785,7 @@ class SemaphoreTests(test_utils.TestCase): self.assertTrue(RGX_REPR.match(repr(sem))) def test_semaphore(self): - with self.assertWarns(DeprecationWarning): - sem = asyncio.Semaphore(loop=self.loop) + sem = asyncio.Semaphore() self.assertEqual(1, sem._value) with self.assertWarns(DeprecationWarning): @@ -872,8 +806,7 @@ class SemaphoreTests(test_utils.TestCase): self.assertRaises(ValueError, asyncio.Semaphore, -1) def test_acquire(self): - with self.assertWarns(DeprecationWarning): - sem = asyncio.Semaphore(3, loop=self.loop) + sem = asyncio.Semaphore(3) result = [] self.assertTrue(self.loop.run_until_complete(sem.acquire())) @@ -934,8 +867,7 @@ class SemaphoreTests(test_utils.TestCase): self.loop.run_until_complete(asyncio.gather(*race_tasks)) def test_acquire_cancel(self): - with self.assertWarns(DeprecationWarning): - sem = asyncio.Semaphore(loop=self.loop) + sem = asyncio.Semaphore() self.loop.run_until_complete(sem.acquire()) acquire = self.loop.create_task(sem.acquire()) @@ -947,8 +879,7 @@ class SemaphoreTests(test_utils.TestCase): all(waiter.done() for waiter in sem._waiters)) def test_acquire_cancel_before_awoken(self): - with self.assertWarns(DeprecationWarning): - sem = asyncio.Semaphore(value=0, loop=self.loop) + sem = asyncio.Semaphore(value=0) t1 = self.loop.create_task(sem.acquire()) t2 = self.loop.create_task(sem.acquire()) @@ -970,8 +901,7 @@ class SemaphoreTests(test_utils.TestCase): test_utils.run_briefly(self.loop) def test_acquire_hang(self): - with self.assertWarns(DeprecationWarning): - sem = asyncio.Semaphore(value=0, loop=self.loop) + sem = asyncio.Semaphore(value=0) t1 = self.loop.create_task(sem.acquire()) t2 = self.loop.create_task(sem.acquire()) @@ -985,14 +915,12 @@ class SemaphoreTests(test_utils.TestCase): self.assertTrue(sem.locked()) def test_release_not_acquired(self): - with self.assertWarns(DeprecationWarning): - sem = asyncio.BoundedSemaphore(loop=self.loop) + sem = asyncio.BoundedSemaphore() self.assertRaises(ValueError, sem.release) def test_release_no_waiters(self): - with self.assertWarns(DeprecationWarning): - sem = asyncio.Semaphore(loop=self.loop) + sem = asyncio.Semaphore() self.loop.run_until_complete(sem.acquire()) self.assertTrue(sem.locked()) diff --git a/Lib/test/test_asyncio/test_pep492.py b/Lib/test/test_asyncio/test_pep492.py index c5e3a5c148..4bd50f4123 100644 --- a/Lib/test/test_asyncio/test_pep492.py +++ b/Lib/test/test_asyncio/test_pep492.py @@ -43,13 +43,12 @@ class BaseTest(test_utils.TestCase): class LockTests(BaseTest): def test_context_manager_async_with(self): - with self.assertWarns(DeprecationWarning): - primitives = [ - asyncio.Lock(loop=self.loop), - asyncio.Condition(loop=self.loop), - asyncio.Semaphore(loop=self.loop), - asyncio.BoundedSemaphore(loop=self.loop), - ] + primitives = [ + asyncio.Lock(), + asyncio.Condition(), + asyncio.Semaphore(), + asyncio.BoundedSemaphore(), + ] async def test(lock): await asyncio.sleep(0.01) @@ -66,13 +65,12 @@ class LockTests(BaseTest): self.assertFalse(primitive.locked()) def test_context_manager_with_await(self): - with self.assertWarns(DeprecationWarning): - primitives = [ - asyncio.Lock(loop=self.loop), - asyncio.Condition(loop=self.loop), - asyncio.Semaphore(loop=self.loop), - asyncio.BoundedSemaphore(loop=self.loop), - ] + primitives = [ + asyncio.Lock(), + asyncio.Condition(), + asyncio.Semaphore(), + asyncio.BoundedSemaphore(), + ] async def test(lock): await asyncio.sleep(0.01) diff --git a/Lib/test/test_asyncio/test_queues.py b/Lib/test/test_asyncio/test_queues.py index 5c9aaa82c3..0a0b529f62 100644 --- a/Lib/test/test_asyncio/test_queues.py +++ b/Lib/test/test_asyncio/test_queues.py @@ -35,14 +35,13 @@ class QueueBasicTests(_QueueTestBase): loop = self.new_test_loop(gen) - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=loop) + q = asyncio.Queue() self.assertTrue(fn(q).startswith('<Queue'), fn(q)) id_is_present = hex(id(q)) in fn(q) self.assertEqual(expect_id, id_is_present) async def add_getter(): - q = asyncio.Queue(loop=loop) + q = asyncio.Queue() # Start a task that waits to get. loop.create_task(q.get()) # Let it start waiting. @@ -51,11 +50,10 @@ class QueueBasicTests(_QueueTestBase): # resume q.get coroutine to finish generator q.put_nowait(0) - with self.assertWarns(DeprecationWarning): - loop.run_until_complete(add_getter()) + loop.run_until_complete(add_getter()) async def add_putter(): - q = asyncio.Queue(maxsize=1, loop=loop) + q = asyncio.Queue(maxsize=1) q.put_nowait(1) # Start a task that waits to put. loop.create_task(q.put(2)) @@ -65,27 +63,11 @@ class QueueBasicTests(_QueueTestBase): # resume q.put coroutine to finish generator q.get_nowait() - with self.assertWarns(DeprecationWarning): - loop.run_until_complete(add_putter()) - q = asyncio.Queue(loop=loop) + loop.run_until_complete(add_putter()) + q = asyncio.Queue() q.put_nowait(1) self.assertTrue('_queue=[1]' in fn(q)) - def test_ctor_loop(self): - loop = mock.Mock() - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=loop) - self.assertIs(q._loop, loop) - - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop) - self.assertIs(q._loop, self.loop) - - def test_ctor_noloop(self): - asyncio.set_event_loop(self.loop) - q = asyncio.Queue() - self.assertIs(q._loop, self.loop) - def test_repr(self): self._test_repr_or_str(repr, True) @@ -93,8 +75,7 @@ class QueueBasicTests(_QueueTestBase): self._test_repr_or_str(str, False) def test_empty(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop) + q = asyncio.Queue() self.assertTrue(q.empty()) q.put_nowait(1) self.assertFalse(q.empty()) @@ -102,18 +83,15 @@ class QueueBasicTests(_QueueTestBase): self.assertTrue(q.empty()) def test_full(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop) + q = asyncio.Queue() self.assertFalse(q.full()) - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(maxsize=1, loop=self.loop) + q = asyncio.Queue(maxsize=1) q.put_nowait(1) self.assertTrue(q.full()) def test_order(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop) + q = asyncio.Queue() for i in [1, 3, 2]: q.put_nowait(i) @@ -131,8 +109,7 @@ class QueueBasicTests(_QueueTestBase): loop = self.new_test_loop(gen) - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(maxsize=2, loop=loop) + q = asyncio.Queue(maxsize=2) self.assertEqual(2, q.maxsize) have_been_put = [] @@ -166,8 +143,7 @@ class QueueBasicTests(_QueueTestBase): class QueueGetTests(_QueueTestBase): def test_blocking_get(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop) + q = asyncio.Queue() q.put_nowait(1) async def queue_get(): @@ -177,8 +153,7 @@ class QueueGetTests(_QueueTestBase): self.assertEqual(1, res) def test_get_with_putters(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(1, loop=self.loop) + q = asyncio.Queue(1) q.put_nowait(1) waiter = self.loop.create_future() @@ -198,9 +173,8 @@ class QueueGetTests(_QueueTestBase): loop = self.new_test_loop(gen) - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=loop) - started = asyncio.Event(loop=loop) + q = asyncio.Queue() + started = asyncio.Event() finished = False async def queue_get(): @@ -224,14 +198,12 @@ class QueueGetTests(_QueueTestBase): self.assertAlmostEqual(0.01, loop.time()) def test_nonblocking_get(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop) + q = asyncio.Queue() q.put_nowait(1) self.assertEqual(1, q.get_nowait()) def test_nonblocking_get_exception(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop) + q = asyncio.Queue() self.assertRaises(asyncio.QueueEmpty, q.get_nowait) def test_get_cancelled(self): @@ -245,8 +217,7 @@ class QueueGetTests(_QueueTestBase): loop = self.new_test_loop(gen) - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=loop) + q = asyncio.Queue() async def queue_get(): return await asyncio.wait_for(q.get(), 0.051) @@ -261,8 +232,7 @@ class QueueGetTests(_QueueTestBase): self.assertAlmostEqual(0.06, loop.time()) def test_get_cancelled_race(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop) + q = asyncio.Queue() t1 = self.loop.create_task(q.get()) t2 = self.loop.create_task(q.get()) @@ -276,8 +246,7 @@ class QueueGetTests(_QueueTestBase): self.assertEqual(t2.result(), 'a') def test_get_with_waiting_putters(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop, maxsize=1) + q = asyncio.Queue(maxsize=1) self.loop.create_task(q.put('a')) self.loop.create_task(q.put('b')) test_utils.run_briefly(self.loop) @@ -286,6 +255,7 @@ class QueueGetTests(_QueueTestBase): def test_why_are_getters_waiting(self): # From issue #268. + asyncio.set_event_loop(self.loop) async def consumer(queue, num_expected): for _ in range(num_expected): @@ -298,13 +268,16 @@ class QueueGetTests(_QueueTestBase): queue_size = 1 producer_num_items = 5 - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(queue_size, loop=self.loop) + async def create_queue(): + queue = asyncio.Queue(queue_size) + queue._get_loop() + return queue + + q = self.loop.run_until_complete(create_queue()) self.loop.run_until_complete( asyncio.gather(producer(q, producer_num_items), - consumer(q, producer_num_items), - loop=self.loop), + consumer(q, producer_num_items)), ) def test_cancelled_getters_not_being_held_in_self_getters(self): @@ -320,8 +293,7 @@ class QueueGetTests(_QueueTestBase): except asyncio.TimeoutError: pass - with self.assertWarns(DeprecationWarning): - queue = asyncio.Queue(loop=self.loop, maxsize=5) + queue = asyncio.Queue(maxsize=5) self.loop.run_until_complete(self.loop.create_task(consumer(queue))) self.assertEqual(len(queue._getters), 0) @@ -329,8 +301,7 @@ class QueueGetTests(_QueueTestBase): class QueuePutTests(_QueueTestBase): def test_blocking_put(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop) + q = asyncio.Queue() async def queue_put(): # No maxsize, won't block. @@ -347,9 +318,8 @@ class QueuePutTests(_QueueTestBase): loop = self.new_test_loop(gen) - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(maxsize=1, loop=loop) - started = asyncio.Event(loop=loop) + q = asyncio.Queue(maxsize=1) + started = asyncio.Event() finished = False async def queue_put(): @@ -371,8 +341,7 @@ class QueuePutTests(_QueueTestBase): self.assertAlmostEqual(0.01, loop.time()) def test_nonblocking_put(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop) + q = asyncio.Queue() q.put_nowait(1) self.assertEqual(1, q.get_nowait()) @@ -383,8 +352,7 @@ class QueuePutTests(_QueueTestBase): loop = self.new_test_loop(gen) - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=loop) + q = asyncio.Queue() reader = loop.create_task(q.get()) @@ -413,8 +381,7 @@ class QueuePutTests(_QueueTestBase): loop = self.new_test_loop(gen) loop.set_debug(True) - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=loop) + q = asyncio.Queue() reader1 = loop.create_task(q.get()) reader2 = loop.create_task(q.get()) @@ -444,8 +411,7 @@ class QueuePutTests(_QueueTestBase): loop = self.new_test_loop(gen) - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(1, loop=loop) + q = asyncio.Queue(1) q.put_nowait(1) @@ -469,21 +435,18 @@ class QueuePutTests(_QueueTestBase): self.assertEqual(q.qsize(), 0) def test_nonblocking_put_exception(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(maxsize=1, loop=self.loop) + q = asyncio.Queue(maxsize=1, ) q.put_nowait(1) self.assertRaises(asyncio.QueueFull, q.put_nowait, 2) def test_float_maxsize(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(maxsize=1.3, loop=self.loop) + q = asyncio.Queue(maxsize=1.3, ) q.put_nowait(1) q.put_nowait(2) self.assertTrue(q.full()) self.assertRaises(asyncio.QueueFull, q.put_nowait, 3) - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(maxsize=1.3, loop=self.loop) + q = asyncio.Queue(maxsize=1.3, ) async def queue_put(): await q.put(1) @@ -492,8 +455,7 @@ class QueuePutTests(_QueueTestBase): self.loop.run_until_complete(queue_put()) def test_put_cancelled(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop) + q = asyncio.Queue() async def queue_put(): await q.put(1) @@ -508,8 +470,7 @@ class QueuePutTests(_QueueTestBase): self.assertTrue(t.result()) def test_put_cancelled_race(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop, maxsize=1) + q = asyncio.Queue(maxsize=1) put_a = self.loop.create_task(q.put('a')) put_b = self.loop.create_task(q.put('b')) @@ -529,8 +490,7 @@ class QueuePutTests(_QueueTestBase): self.loop.run_until_complete(put_b) def test_put_with_waiting_getters(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.Queue(loop=self.loop) + q = asyncio.Queue() t = self.loop.create_task(q.get()) test_utils.run_briefly(self.loop) self.loop.run_until_complete(q.put('a')) @@ -538,9 +498,14 @@ class QueuePutTests(_QueueTestBase): def test_why_are_putters_waiting(self): # From issue #265. + asyncio.set_event_loop(self.loop) + + async def create_queue(): + q = asyncio.Queue(2) + q._get_loop() + return q - with self.assertWarns(DeprecationWarning): - queue = asyncio.Queue(2, loop=self.loop) + queue = self.loop.run_until_complete(create_queue()) async def putter(item): await queue.put(item) @@ -555,8 +520,7 @@ class QueuePutTests(_QueueTestBase): t1 = putter(1) t2 = putter(2) t3 = putter(3) - self.loop.run_until_complete( - asyncio.gather(getter(), t0, t1, t2, t3, loop=self.loop)) + self.loop.run_until_complete(asyncio.gather(getter(), t0, t1, t2, t3)) def test_cancelled_puts_not_being_held_in_self_putters(self): def a_generator(): @@ -566,8 +530,7 @@ class QueuePutTests(_QueueTestBase): loop = self.new_test_loop(a_generator) # Full queue. - with self.assertWarns(DeprecationWarning): - queue = asyncio.Queue(loop=loop, maxsize=1) + queue = asyncio.Queue(maxsize=1) queue.put_nowait(1) # Task waiting for space to put an item in the queue. @@ -590,8 +553,7 @@ class QueuePutTests(_QueueTestBase): loop = self.new_test_loop(gen) # Full Queue. - with self.assertWarns(DeprecationWarning): - queue = asyncio.Queue(1, loop=loop) + queue = asyncio.Queue(1) queue.put_nowait(1) # Task waiting for space to put a item in the queue. @@ -614,8 +576,7 @@ class QueuePutTests(_QueueTestBase): class LifoQueueTests(_QueueTestBase): def test_order(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.LifoQueue(loop=self.loop) + q = asyncio.LifoQueue() for i in [1, 3, 2]: q.put_nowait(i) @@ -626,8 +587,7 @@ class LifoQueueTests(_QueueTestBase): class PriorityQueueTests(_QueueTestBase): def test_order(self): - with self.assertWarns(DeprecationWarning): - q = asyncio.PriorityQueue(loop=self.loop) + q = asyncio.PriorityQueue() for i in [1, 3, 2]: q.put_nowait(i) @@ -640,13 +600,11 @@ class _QueueJoinTestMixin: q_class = None def test_task_done_underflow(self): - with self.assertWarns(DeprecationWarning): - q = self.q_class(loop=self.loop) + q = self.q_class() self.assertRaises(ValueError, q.task_done) def test_task_done(self): - with self.assertWarns(DeprecationWarning): - q = self.q_class(loop=self.loop) + q = self.q_class() for i in range(100): q.put_nowait(i) @@ -681,8 +639,7 @@ class _QueueJoinTestMixin: self.loop.run_until_complete(asyncio.wait(tasks)) def test_join_empty_queue(self): - with self.assertWarns(DeprecationWarning): - q = self.q_class(loop=self.loop) + q = self.q_class() # Test that a queue join()s successfully, and before anything else # (done twice for insurance). @@ -694,8 +651,7 @@ class _QueueJoinTestMixin: self.loop.run_until_complete(join()) def test_format(self): - with self.assertWarns(DeprecationWarning): - q = self.q_class(loop=self.loop) + q = self.q_class() self.assertEqual(q._format(), 'maxsize=0') q._unfinished_tasks = 2 diff --git a/Lib/test/test_asyncio/test_server.py b/Lib/test/test_asyncio/test_server.py index 2de4dcad17..860d62d52e 100644 --- a/Lib/test/test_asyncio/test_server.py +++ b/Lib/test/test_asyncio/test_server.py @@ -45,9 +45,8 @@ class BaseStartServer(func_tests.FunctionalTestCaseMixin): async with srv: await srv.serve_forever() - with self.assertWarns(DeprecationWarning): - srv = self.loop.run_until_complete(asyncio.start_server( - serve, socket_helper.HOSTv4, 0, loop=self.loop, start_serving=False)) + srv = self.loop.run_until_complete(asyncio.start_server( + serve, socket_helper.HOSTv4, 0, start_serving=False)) self.assertFalse(srv.is_serving()) @@ -102,9 +101,8 @@ class SelectorStartServerTests(BaseStartServer, unittest.TestCase): await srv.serve_forever() with test_utils.unix_socket_path() as addr: - with self.assertWarns(DeprecationWarning): - srv = self.loop.run_until_complete(asyncio.start_unix_server( - serve, addr, loop=self.loop, start_serving=False)) + srv = self.loop.run_until_complete(asyncio.start_unix_server( + serve, addr, start_serving=False)) main_task = self.loop.create_task(main(srv)) diff --git a/Lib/test/test_asyncio/test_sslproto.py b/Lib/test/test_asyncio/test_sslproto.py index 948820c82f..e87863eb71 100644 --- a/Lib/test/test_asyncio/test_sslproto.py +++ b/Lib/test/test_asyncio/test_sslproto.py @@ -657,13 +657,11 @@ class BaseStartTLS(func_tests.FunctionalTestCaseMixin): sock.close() async def client(addr): - with self.assertWarns(DeprecationWarning): - reader, writer = await asyncio.open_connection( - *addr, - ssl=client_sslctx, - server_hostname='', - loop=self.loop, - ssl_handshake_timeout=1.0) + reader, writer = await asyncio.open_connection( + *addr, + ssl=client_sslctx, + server_hostname='', + ssl_handshake_timeout=1.0) with self.tcp_server(server, max_clients=1, @@ -697,13 +695,11 @@ class BaseStartTLS(func_tests.FunctionalTestCaseMixin): sock.close() async def client(addr): - with self.assertWarns(DeprecationWarning): - reader, writer = await asyncio.open_connection( - *addr, - ssl=client_sslctx, - server_hostname='', - loop=self.loop, - ssl_handshake_timeout=support.LOOPBACK_TIMEOUT) + reader, writer = await asyncio.open_connection( + *addr, + ssl=client_sslctx, + server_hostname='', + ssl_handshake_timeout=support.LOOPBACK_TIMEOUT) with self.tcp_server(server, max_clients=1, @@ -734,12 +730,10 @@ class BaseStartTLS(func_tests.FunctionalTestCaseMixin): sock.close() async def client(addr): - with self.assertWarns(DeprecationWarning): - reader, writer = await asyncio.open_connection( - *addr, - ssl=client_sslctx, - server_hostname='', - loop=self.loop) + reader, writer = await asyncio.open_connection( + *addr, + ssl=client_sslctx, + server_hostname='') self.assertEqual(await reader.readline(), b'A\n') writer.write(b'B') diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 1e9d115661..aa39779775 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -48,8 +48,7 @@ class StreamTests(test_utils.TestCase): def _basetest_open_connection(self, open_connection_fut): messages = [] self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx)) - with self.assertWarns(DeprecationWarning): - reader, writer = self.loop.run_until_complete(open_connection_fut) + reader, writer = self.loop.run_until_complete(open_connection_fut) writer.write(b'GET / HTTP/1.0\r\n\r\n') f = reader.readline() data = self.loop.run_until_complete(f) @@ -62,23 +61,20 @@ class StreamTests(test_utils.TestCase): def test_open_connection(self): with test_utils.run_test_server() as httpd: - conn_fut = asyncio.open_connection(*httpd.address, - loop=self.loop) + conn_fut = asyncio.open_connection(*httpd.address) self._basetest_open_connection(conn_fut) @socket_helper.skip_unless_bind_unix_socket def test_open_unix_connection(self): with test_utils.run_test_unix_server() as httpd: - conn_fut = asyncio.open_unix_connection(httpd.address, - loop=self.loop) + conn_fut = asyncio.open_unix_connection(httpd.address) self._basetest_open_connection(conn_fut) def _basetest_open_connection_no_loop_ssl(self, open_connection_fut): messages = [] self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx)) try: - with self.assertWarns(DeprecationWarning): - reader, writer = self.loop.run_until_complete(open_connection_fut) + reader, writer = self.loop.run_until_complete(open_connection_fut) finally: asyncio.set_event_loop(None) writer.write(b'GET / HTTP/1.0\r\n\r\n') @@ -94,8 +90,7 @@ class StreamTests(test_utils.TestCase): with test_utils.run_test_server(use_ssl=True) as httpd: conn_fut = asyncio.open_connection( *httpd.address, - ssl=test_utils.dummy_ssl_context(), - loop=self.loop) + ssl=test_utils.dummy_ssl_context()) self._basetest_open_connection_no_loop_ssl(conn_fut) @@ -107,15 +102,14 @@ class StreamTests(test_utils.TestCase): httpd.address, ssl=test_utils.dummy_ssl_context(), server_hostname='', - loop=self.loop) + ) self._basetest_open_connection_no_loop_ssl(conn_fut) def _basetest_open_connection_error(self, open_connection_fut): messages = [] self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx)) - with self.assertWarns(DeprecationWarning): - reader, writer = self.loop.run_until_complete(open_connection_fut) + reader, writer = self.loop.run_until_complete(open_connection_fut) writer._protocol.connection_lost(ZeroDivisionError()) f = reader.read() with self.assertRaises(ZeroDivisionError): @@ -126,15 +120,13 @@ class StreamTests(test_utils.TestCase): def test_open_connection_error(self): with test_utils.run_test_server() as httpd: - conn_fut = asyncio.open_connection(*httpd.address, - loop=self.loop) + conn_fut = asyncio.open_connection(*httpd.address) self._basetest_open_connection_error(conn_fut) @socket_helper.skip_unless_bind_unix_socket def test_open_unix_connection_error(self): with test_utils.run_test_unix_server() as httpd: - conn_fut = asyncio.open_unix_connection(httpd.address, - loop=self.loop) + conn_fut = asyncio.open_unix_connection(httpd.address) self._basetest_open_connection_error(conn_fut) def test_feed_empty_data(self): @@ -452,12 +444,14 @@ class StreamTests(test_utils.TestCase): def test_readuntil_eof(self): stream = asyncio.StreamReader(loop=self.loop) - stream.feed_data(b'some dataAA') + data = b'some dataAA' + stream.feed_data(data) stream.feed_eof() - with self.assertRaises(asyncio.IncompleteReadError) as cm: + with self.assertRaisesRegex(asyncio.IncompleteReadError, + 'undefined expected bytes') as cm: self.loop.run_until_complete(stream.readuntil(b'AAA')) - self.assertEqual(cm.exception.partial, b'some dataAA') + self.assertEqual(cm.exception.partial, data) self.assertIsNone(cm.exception.expected) self.assertEqual(b'', stream._buffer) @@ -596,8 +590,7 @@ class StreamTests(test_utils.TestCase): sock = socket.create_server(('127.0.0.1', 0)) self.server = self.loop.run_until_complete( asyncio.start_server(self.handle_client, - sock=sock, - loop=self.loop)) + sock=sock)) return sock.getsockname() def handle_client_callback(self, client_reader, client_writer): @@ -610,8 +603,7 @@ class StreamTests(test_utils.TestCase): sock.close() self.server = self.loop.run_until_complete( asyncio.start_server(self.handle_client_callback, - host=addr[0], port=addr[1], - loop=self.loop)) + host=addr[0], port=addr[1])) return addr def stop(self): @@ -621,9 +613,7 @@ class StreamTests(test_utils.TestCase): self.server = None async def client(addr): - with self.assertWarns(DeprecationWarning): - reader, writer = await asyncio.open_connection( - *addr, loop=self.loop) + reader, writer = await asyncio.open_connection(*addr) # send a line writer.write(b"hello world!\n") # read it back @@ -637,16 +627,14 @@ class StreamTests(test_utils.TestCase): # test the server variant with a coroutine as client handler server = MyServer(self.loop) - with self.assertWarns(DeprecationWarning): - addr = server.start() + addr = server.start() msg = self.loop.run_until_complete(self.loop.create_task(client(addr))) server.stop() self.assertEqual(msg, b"hello world!\n") # test the server variant with a callback as client handler server = MyServer(self.loop) - with self.assertWarns(DeprecationWarning): - addr = server.start_callback() + addr = server.start_callback() msg = self.loop.run_until_complete(self.loop.create_task(client(addr))) server.stop() self.assertEqual(msg, b"hello world!\n") @@ -673,8 +661,7 @@ class StreamTests(test_utils.TestCase): def start(self): self.server = self.loop.run_until_complete( asyncio.start_unix_server(self.handle_client, - path=self.path, - loop=self.loop)) + path=self.path)) def handle_client_callback(self, client_reader, client_writer): self.loop.create_task(self.handle_client(client_reader, @@ -682,8 +669,7 @@ class StreamTests(test_utils.TestCase): def start_callback(self): start = asyncio.start_unix_server(self.handle_client_callback, - path=self.path, - loop=self.loop) + path=self.path) self.server = self.loop.run_until_complete(start) def stop(self): @@ -693,9 +679,7 @@ class StreamTests(test_utils.TestCase): self.server = None async def client(path): - with self.assertWarns(DeprecationWarning): - reader, writer = await asyncio.open_unix_connection( - path, loop=self.loop) + reader, writer = await asyncio.open_unix_connection(path) # send a line writer.write(b"hello world!\n") # read it back @@ -710,8 +694,7 @@ class StreamTests(test_utils.TestCase): # test the server variant with a coroutine as client handler with test_utils.unix_socket_path() as path: server = MyServer(self.loop, path) - with self.assertWarns(DeprecationWarning): - server.start() + server.start() msg = self.loop.run_until_complete( self.loop.create_task(client(path))) server.stop() @@ -720,8 +703,7 @@ class StreamTests(test_utils.TestCase): # test the server variant with a callback as client handler with test_utils.unix_socket_path() as path: server = MyServer(self.loop, path) - with self.assertWarns(DeprecationWarning): - server.start_callback() + server.start_callback() msg = self.loop.run_until_complete( self.loop.create_task(client(path))) server.stop() @@ -809,9 +791,7 @@ os.close(fd) clt.close() async def client(host, port): - with self.assertWarns(DeprecationWarning): - reader, writer = await asyncio.open_connection( - host, port, loop=self.loop) + reader, writer = await asyncio.open_connection(host, port) while True: writer.write(b"foo\n") @@ -895,9 +875,8 @@ os.close(fd) def test_wait_closed_on_close(self): with test_utils.run_test_server() as httpd: - with self.assertWarns(DeprecationWarning): - rd, wr = self.loop.run_until_complete( - asyncio.open_connection(*httpd.address, loop=self.loop)) + rd, wr = self.loop.run_until_complete( + asyncio.open_connection(*httpd.address)) wr.write(b'GET / HTTP/1.0\r\n\r\n') f = rd.readline() @@ -913,9 +892,8 @@ os.close(fd) def test_wait_closed_on_close_with_unread_data(self): with test_utils.run_test_server() as httpd: - with self.assertWarns(DeprecationWarning): - rd, wr = self.loop.run_until_complete( - asyncio.open_connection(*httpd.address, loop=self.loop)) + rd, wr = self.loop.run_until_complete( + asyncio.open_connection(*httpd.address)) wr.write(b'GET / HTTP/1.0\r\n\r\n') f = rd.readline() @@ -972,10 +950,8 @@ os.close(fd) self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx)) with test_utils.run_test_server() as httpd: - with self.assertWarns(DeprecationWarning): - rd, wr = self.loop.run_until_complete( - asyncio.open_connection(*httpd.address, - loop=self.loop)) + rd, wr = self.loop.run_until_complete( + asyncio.open_connection(*httpd.address)) wr.close() f = wr.wait_closed() diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py index 177a02cdcc..225a3babc8 100644 --- a/Lib/test/test_asyncio/test_subprocess.py +++ b/Lib/test/test_asyncio/test_subprocess.py @@ -635,26 +635,6 @@ class SubprocessMixin: self.assertIsNone(self.loop.run_until_complete(execute())) - def test_exec_loop_deprecated(self): - async def go(): - with self.assertWarns(DeprecationWarning): - proc = await asyncio.create_subprocess_exec( - sys.executable, '-c', 'pass', - loop=self.loop, - ) - await proc.wait() - self.loop.run_until_complete(go()) - - def test_shell_loop_deprecated(self): - async def go(): - with self.assertWarns(DeprecationWarning): - proc = await asyncio.create_subprocess_shell( - "exit 0", - loop=self.loop, - ) - await proc.wait() - self.loop.run_until_complete(go()) - if sys.platform != 'win32': # Unix diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 74fc1e4a42..7c2e85ceef 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -155,7 +155,7 @@ class BaseTaskTests: self.loop.run_until_complete( asyncio.gather(*[ self.new_task(self.loop, run()) for _ in range(100) - ], loop=self.loop)) + ])) def test_other_loop_future(self): other_loop = asyncio.new_event_loop() @@ -1548,6 +1548,30 @@ class BaseTaskTests: loop.advance_time(10) loop.run_until_complete(asyncio.wait([a, b])) + def test_wait_with_iterator_of_tasks(self): + + def gen(): + when = yield + self.assertAlmostEqual(0.1, when) + when = yield 0 + self.assertAlmostEqual(0.15, when) + yield 0.15 + + loop = self.new_test_loop(gen) + + a = self.new_task(loop, asyncio.sleep(0.1)) + b = self.new_task(loop, asyncio.sleep(0.15)) + + async def foo(): + done, pending = await asyncio.wait(iter([b, a])) + self.assertEqual(done, set([a, b])) + self.assertEqual(pending, set()) + return 42 + + res = loop.run_until_complete(self.new_task(loop, foo())) + self.assertEqual(res, 42) + self.assertAlmostEqual(0.15, loop.time()) + def test_as_completed(self): def gen(): @@ -1579,19 +1603,18 @@ class BaseTaskTests: async def foo(): values = [] - for f in asyncio.as_completed([b, c, a], loop=loop): + for f in asyncio.as_completed([b, c, a]): values.append(await f) return values - with self.assertWarns(DeprecationWarning): - res = loop.run_until_complete(self.new_task(loop, foo())) + + res = loop.run_until_complete(self.new_task(loop, foo())) self.assertAlmostEqual(0.15, loop.time()) self.assertTrue('a' in res[:2]) self.assertTrue('b' in res[:2]) self.assertEqual(res[2], 'c') # Doing it again should take no time and exercise a different path. - with self.assertWarns(DeprecationWarning): - res = loop.run_until_complete(self.new_task(loop, foo())) + res = loop.run_until_complete(self.new_task(loop, foo())) self.assertAlmostEqual(0.15, loop.time()) def test_as_completed_with_timeout(self): @@ -1609,7 +1632,7 @@ class BaseTaskTests: async def foo(): values = [] - for f in asyncio.as_completed([a, b], timeout=0.12, loop=loop): + for f in asyncio.as_completed([a, b], timeout=0.12): if values: loop.advance_time(0.02) try: @@ -1619,8 +1642,7 @@ class BaseTaskTests: values.append((2, exc)) return values - with self.assertWarns(DeprecationWarning): - res = loop.run_until_complete(self.new_task(loop, foo())) + res = loop.run_until_complete(self.new_task(loop, foo())) self.assertEqual(len(res), 2, res) self.assertEqual(res[0], (1, 'a')) self.assertEqual(res[1][0], 2) @@ -1643,12 +1665,11 @@ class BaseTaskTests: a = asyncio.sleep(0.01, 'a') async def foo(): - for f in asyncio.as_completed([a], timeout=1, loop=loop): + for f in asyncio.as_completed([a], timeout=1): v = await f self.assertEqual(v, 'a') - with self.assertWarns(DeprecationWarning): - loop.run_until_complete(self.new_task(loop, foo())) + loop.run_until_complete(self.new_task(loop, foo())) def test_as_completed_reverse_wait(self): @@ -1658,13 +1679,13 @@ class BaseTaskTests: yield 0 loop = self.new_test_loop(gen) + asyncio.set_event_loop(loop) a = asyncio.sleep(0.05, 'a') b = asyncio.sleep(0.10, 'b') fs = {a, b} - with self.assertWarns(DeprecationWarning): - futs = list(asyncio.as_completed(fs, loop=loop)) + futs = list(asyncio.as_completed(fs)) self.assertEqual(len(futs), 2) x = loop.run_until_complete(futs[1]) @@ -1685,12 +1706,13 @@ class BaseTaskTests: yield 0.05 loop = self.new_test_loop(gen) + asyncio.set_event_loop(loop) a = asyncio.sleep(0.05, 'a') b = asyncio.sleep(0.05, 'b') fs = {a, b} - with self.assertWarns(DeprecationWarning): - futs = list(asyncio.as_completed(fs, loop=loop)) + + futs = list(asyncio.as_completed(fs)) self.assertEqual(len(futs), 2) waiter = asyncio.wait(futs) # Deprecation from passing coros in futs to asyncio.wait() @@ -1710,14 +1732,12 @@ class BaseTaskTests: def runner(): result = [] c = coro('ham') - for f in asyncio.as_completed([c, c, coro('spam')], - loop=self.loop): + for f in asyncio.as_completed([c, c, coro('spam')]): result.append((yield from f)) return result - with self.assertWarns(DeprecationWarning): - fut = self.new_task(self.loop, runner()) - self.loop.run_until_complete(fut) + fut = self.new_task(self.loop, runner()) + self.loop.run_until_complete(fut) result = fut.result() self.assertEqual(set(result), {'ham', 'spam'}) self.assertEqual(len(result), 2) @@ -1994,7 +2014,7 @@ class BaseTaskTests: self.assertIsNone(asyncio.current_task(loop=self.loop)) async def coro(loop): - self.assertIs(asyncio.current_task(loop=loop), task) + self.assertIs(asyncio.current_task(), task) self.assertIs(asyncio.current_task(None), task) self.assertIs(asyncio.current_task(), task) @@ -2010,16 +2030,16 @@ class BaseTaskTests: fut2 = self.new_future(self.loop) async def coro1(loop): - self.assertTrue(asyncio.current_task(loop=loop) is task1) + self.assertTrue(asyncio.current_task() is task1) await fut1 - self.assertTrue(asyncio.current_task(loop=loop) is task1) + self.assertTrue(asyncio.current_task() is task1) fut2.set_result(True) async def coro2(loop): - self.assertTrue(asyncio.current_task(loop=loop) is task2) + self.assertTrue(asyncio.current_task() is task2) fut1.set_result(True) await fut2 - self.assertTrue(asyncio.current_task(loop=loop) is task2) + self.assertTrue(asyncio.current_task() is task2) task1 = self.new_task(self.loop, coro1(self.loop)) task2 = self.new_task(self.loop, coro2(self.loop)) @@ -2186,10 +2206,10 @@ class BaseTaskTests: # as_completed() expects a list of futures, not a future instance self.assertRaises(TypeError, self.loop.run_until_complete, - asyncio.as_completed(fut, loop=self.loop)) + asyncio.as_completed(fut)) coro = coroutine_function() self.assertRaises(TypeError, self.loop.run_until_complete, - asyncio.as_completed(coro, loop=self.loop)) + asyncio.as_completed(coro)) coro.close() def test_wait_invalid_args(self): @@ -2487,6 +2507,7 @@ class BaseTaskTests: """Ensure that a gathering future refuses to be cancelled once all children are done""" loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) self.addCleanup(loop.close) fut = self.new_future(loop) @@ -2494,7 +2515,7 @@ class BaseTaskTests: # gathering task is done at the same time as the child future def child_coro(): return (yield from fut) - gather_future = asyncio.gather(child_coro(), loop=loop) + gather_future = asyncio.gather(child_coro()) gather_task = asyncio.ensure_future(gather_future, loop=loop) cancel_result = None @@ -2531,8 +2552,7 @@ class BaseTaskTests: while True: time += 0.05 await asyncio.gather(asyncio.sleep(0.05), - return_exceptions=True, - loop=loop) + return_exceptions=True) if time > 1: return @@ -2747,7 +2767,7 @@ class BaseTaskTests: task = loop.create_task(sub(random.randint(0, 10))) tasks.append(task) - await asyncio.gather(*tasks, loop=loop) + await asyncio.gather(*tasks) loop = asyncio.new_event_loop() try: @@ -3304,9 +3324,6 @@ class FutureGatherTests(GatherTestsBase, test_utils.TestCase): self._run_loop(self.one_loop) self.assertTrue(fut.done()) self.assertEqual(fut.result(), []) - with self.assertWarns(DeprecationWarning): - fut = asyncio.gather(*seq_or_iter, loop=self.other_loop) - self.assertIs(fut._loop, self.other_loop) def test_constructor_empty_sequence(self): self._check_empty_sequence([]) @@ -3319,8 +3336,6 @@ class FutureGatherTests(GatherTestsBase, test_utils.TestCase): fut2 = self.other_loop.create_future() with self.assertRaises(ValueError): asyncio.gather(fut1, fut2) - with self.assertRaises(ValueError): - asyncio.gather(fut1, loop=self.other_loop) def test_constructor_homogenous_futures(self): children = [self.other_loop.create_future() for i in range(3)] @@ -3328,7 +3343,7 @@ class FutureGatherTests(GatherTestsBase, test_utils.TestCase): self.assertIs(fut._loop, self.other_loop) self._run_loop(self.other_loop) self.assertFalse(fut.done()) - fut = asyncio.gather(*children, loop=self.other_loop) + fut = asyncio.gather(*children) self.assertIs(fut._loop, self.other_loop) self._run_loop(self.other_loop) self.assertFalse(fut.done()) @@ -3399,9 +3414,10 @@ class CoroutineGatherTests(GatherTestsBase, test_utils.TestCase): self.one_loop.run_until_complete(fut) self.set_event_loop(self.other_loop, cleanup=False) + asyncio.set_event_loop(self.other_loop) gen3 = coro() gen4 = coro() - fut2 = asyncio.gather(gen3, gen4, loop=self.other_loop) + fut2 = asyncio.gather(gen3, gen4) self.assertIs(fut2._loop, self.other_loop) self.other_loop.run_until_complete(fut2) @@ -3411,7 +3427,7 @@ class CoroutineGatherTests(GatherTestsBase, test_utils.TestCase): def coro(s): return s c = coro('abc') - fut = asyncio.gather(c, c, coro('def'), c, loop=self.one_loop) + fut = asyncio.gather(c, c, coro('def'), c) self._run_loop(self.one_loop) self.assertEqual(fut.result(), ['abc', 'abc', 'def', 'abc']) @@ -3431,7 +3447,7 @@ class CoroutineGatherTests(GatherTestsBase, test_utils.TestCase): async def outer(): nonlocal proof, gatherer - gatherer = asyncio.gather(child1, child2, loop=self.one_loop) + gatherer = asyncio.gather(child1, child2) await gatherer proof += 100 @@ -3458,7 +3474,7 @@ class CoroutineGatherTests(GatherTestsBase, test_utils.TestCase): b = self.one_loop.create_future() async def outer(): - await asyncio.gather(inner(a), inner(b), loop=self.one_loop) + await asyncio.gather(inner(a), inner(b)) f = asyncio.ensure_future(outer(), loop=self.one_loop) test_utils.run_briefly(self.one_loop) @@ -3597,11 +3613,6 @@ class SleepTests(test_utils.TestCase): self.loop.run_until_complete(coro()) self.assertEqual(result, 11) - def test_loop_argument_is_deprecated(self): - # Remove test when loop argument is removed in Python 3.10 - with self.assertWarns(DeprecationWarning): - self.loop.run_until_complete(asyncio.sleep(0.01, loop=self.loop)) - class WaitTests(test_utils.TestCase): def setUp(self): @@ -3614,18 +3625,6 @@ class WaitTests(test_utils.TestCase): self.loop = None super().tearDown() - def test_loop_argument_is_deprecated_in_wait(self): - # Remove test when loop argument is removed in Python 3.10 - with self.assertWarns(DeprecationWarning): - self.loop.run_until_complete( - asyncio.wait([coroutine_function()], loop=self.loop)) - - def test_loop_argument_is_deprecated_in_wait_for(self): - # Remove test when loop argument is removed in Python 3.10 - with self.assertWarns(DeprecationWarning): - self.loop.run_until_complete( - asyncio.wait_for(coroutine_function(), 0.01, loop=self.loop)) - def test_coro_is_deprecated_in_wait(self): # Remove test when passing coros to asyncio.wait() is removed in 3.11 with self.assertWarns(DeprecationWarning): @@ -3677,7 +3676,7 @@ class CompatibilityTests(test_utils.TestCase): return 'ok2' async def inner(): - return await asyncio.gather(coro1(), coro2(), loop=self.loop) + return await asyncio.gather(coro1(), coro2()) result = self.loop.run_until_complete(inner()) self.assertEqual(['ok1', 'ok2'], result) diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py index 2c7d52a15b..643638564e 100644 --- a/Lib/test/test_asyncio/test_unix_events.py +++ b/Lib/test/test_asyncio/test_unix_events.py @@ -1,6 +1,5 @@ """Tests for unix_events.py.""" -import collections import contextlib import errno import io @@ -30,6 +29,15 @@ from test.test_asyncio import utils as test_utils MOCK_ANY = mock.ANY +def EXITCODE(exitcode): + return 32768 + exitcode + + +def SIGNAL(signum): + assert 1 <= signum <= 68 + return 32768 - signum + + def tearDownModule(): asyncio.set_event_loop_policy(None) @@ -1125,15 +1133,6 @@ class BaseChildWatcherTests(unittest.TestCase): NotImplementedError, watcher._do_waitpid, f) -WaitPidMocks = collections.namedtuple("WaitPidMocks", - ("waitpid", - "WIFEXITED", - "WIFSIGNALED", - "WEXITSTATUS", - "WTERMSIG", - )) - - class ChildWatcherTestsMixin: ignore_warnings = mock.patch.object(log.logger, "warning") @@ -1164,22 +1163,16 @@ class ChildWatcherTestsMixin: else: raise ChildProcessError() - def add_zombie(self, pid, returncode): - self.zombies[pid] = returncode + 32768 - - def WIFEXITED(self, status): - return status >= 32768 + def add_zombie(self, pid, status): + self.zombies[pid] = status - def WIFSIGNALED(self, status): - return 32700 < status < 32768 - - def WEXITSTATUS(self, status): - self.assertTrue(self.WIFEXITED(status)) - return status - 32768 - - def WTERMSIG(self, status): - self.assertTrue(self.WIFSIGNALED(status)) - return 32768 - status + def waitstatus_to_exitcode(self, status): + if status > 32768: + return status - 32768 + elif 32700 < status < 32768: + return status - 32768 + else: + return status def test_create_watcher(self): self.m_add_signal_handler.assert_called_once_with( @@ -1191,19 +1184,13 @@ class ChildWatcherTestsMixin: return mock.patch(target, wraps=wrapper, new_callable=mock.Mock) - with patch('os.WTERMSIG', self.WTERMSIG) as m_WTERMSIG, \ - patch('os.WEXITSTATUS', self.WEXITSTATUS) as m_WEXITSTATUS, \ - patch('os.WIFSIGNALED', self.WIFSIGNALED) as m_WIFSIGNALED, \ - patch('os.WIFEXITED', self.WIFEXITED) as m_WIFEXITED, \ + with patch('asyncio.unix_events.waitstatus_to_exitcode', self.waitstatus_to_exitcode), \ patch('os.waitpid', self.waitpid) as m_waitpid: - func(self, WaitPidMocks(m_waitpid, - m_WIFEXITED, m_WIFSIGNALED, - m_WEXITSTATUS, m_WTERMSIG, - )) + func(self, m_waitpid) return wrapped_func @waitpid_mocks - def test_sigchld(self, m): + def test_sigchld(self, m_waitpid): # register a child callback = mock.Mock() @@ -1212,59 +1199,36 @@ class ChildWatcherTestsMixin: self.watcher.add_child_handler(42, callback, 9, 10, 14) self.assertFalse(callback.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # child is running self.watcher._sig_chld() self.assertFalse(callback.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # child terminates (returncode 12) self.running = False - self.add_zombie(42, 12) + self.add_zombie(42, EXITCODE(12)) self.watcher._sig_chld() - self.assertTrue(m.WIFEXITED.called) - self.assertTrue(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) callback.assert_called_once_with(42, 12, 9, 10, 14) - m.WIFSIGNALED.reset_mock() - m.WIFEXITED.reset_mock() - m.WEXITSTATUS.reset_mock() callback.reset_mock() # ensure that the child is effectively reaped - self.add_zombie(42, 13) + self.add_zombie(42, EXITCODE(13)) with self.ignore_warnings: self.watcher._sig_chld() self.assertFalse(callback.called) - self.assertFalse(m.WTERMSIG.called) - - m.WIFSIGNALED.reset_mock() - m.WIFEXITED.reset_mock() - m.WEXITSTATUS.reset_mock() # sigchld called again self.zombies.clear() self.watcher._sig_chld() self.assertFalse(callback.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) @waitpid_mocks - def test_sigchld_two_children(self, m): + def test_sigchld_two_children(self, m_waitpid): callback1 = mock.Mock() callback2 = mock.Mock() @@ -1275,10 +1239,6 @@ class ChildWatcherTestsMixin: self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # register child 2 with self.watcher: @@ -1286,34 +1246,20 @@ class ChildWatcherTestsMixin: self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # children are running self.watcher._sig_chld() self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # child 1 terminates (signal 3) - self.add_zombie(43, -3) + self.add_zombie(43, SIGNAL(3)) self.watcher._sig_chld() callback1.assert_called_once_with(43, -3, 7, 8) self.assertFalse(callback2.called) - self.assertTrue(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertTrue(m.WTERMSIG.called) - m.WIFSIGNALED.reset_mock() - m.WIFEXITED.reset_mock() - m.WTERMSIG.reset_mock() callback1.reset_mock() # child 2 still running @@ -1321,40 +1267,25 @@ class ChildWatcherTestsMixin: self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # child 2 terminates (code 108) - self.add_zombie(44, 108) + self.add_zombie(44, EXITCODE(108)) self.running = False self.watcher._sig_chld() callback2.assert_called_once_with(44, 108, 147, 18) self.assertFalse(callback1.called) - self.assertTrue(m.WIFEXITED.called) - self.assertTrue(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) - m.WIFSIGNALED.reset_mock() - m.WIFEXITED.reset_mock() - m.WEXITSTATUS.reset_mock() callback2.reset_mock() # ensure that the children are effectively reaped - self.add_zombie(43, 14) - self.add_zombie(44, 15) + self.add_zombie(43, EXITCODE(14)) + self.add_zombie(44, EXITCODE(15)) with self.ignore_warnings: self.watcher._sig_chld() self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WTERMSIG.called) - - m.WIFSIGNALED.reset_mock() - m.WIFEXITED.reset_mock() - m.WEXITSTATUS.reset_mock() # sigchld called again self.zombies.clear() @@ -1362,13 +1293,9 @@ class ChildWatcherTestsMixin: self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) @waitpid_mocks - def test_sigchld_two_children_terminating_together(self, m): + def test_sigchld_two_children_terminating_together(self, m_waitpid): callback1 = mock.Mock() callback2 = mock.Mock() @@ -1379,10 +1306,6 @@ class ChildWatcherTestsMixin: self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # register child 2 with self.watcher: @@ -1390,60 +1313,43 @@ class ChildWatcherTestsMixin: self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # children are running self.watcher._sig_chld() self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # child 1 terminates (code 78) # child 2 terminates (signal 5) - self.add_zombie(45, 78) - self.add_zombie(46, -5) + self.add_zombie(45, EXITCODE(78)) + self.add_zombie(46, SIGNAL(5)) self.running = False self.watcher._sig_chld() callback1.assert_called_once_with(45, 78, 17, 8) callback2.assert_called_once_with(46, -5, 1147, 18) - self.assertTrue(m.WIFSIGNALED.called) - self.assertTrue(m.WIFEXITED.called) - self.assertTrue(m.WEXITSTATUS.called) - self.assertTrue(m.WTERMSIG.called) - - m.WIFSIGNALED.reset_mock() - m.WIFEXITED.reset_mock() - m.WTERMSIG.reset_mock() - m.WEXITSTATUS.reset_mock() + callback1.reset_mock() callback2.reset_mock() # ensure that the children are effectively reaped - self.add_zombie(45, 14) - self.add_zombie(46, 15) + self.add_zombie(45, EXITCODE(14)) + self.add_zombie(46, EXITCODE(15)) with self.ignore_warnings: self.watcher._sig_chld() self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WTERMSIG.called) @waitpid_mocks - def test_sigchld_race_condition(self, m): + def test_sigchld_race_condition(self, m_waitpid): # register a child callback = mock.Mock() with self.watcher: # child terminates before being registered - self.add_zombie(50, 4) + self.add_zombie(50, EXITCODE(4)) self.watcher._sig_chld() self.watcher.add_child_handler(50, callback, 1, 12) @@ -1452,14 +1358,14 @@ class ChildWatcherTestsMixin: callback.reset_mock() # ensure that the child is effectively reaped - self.add_zombie(50, -1) + self.add_zombie(50, SIGNAL(1)) with self.ignore_warnings: self.watcher._sig_chld() self.assertFalse(callback.called) @waitpid_mocks - def test_sigchld_replace_handler(self, m): + def test_sigchld_replace_handler(self, m_waitpid): callback1 = mock.Mock() callback2 = mock.Mock() @@ -1470,10 +1376,6 @@ class ChildWatcherTestsMixin: self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # register the same child again with self.watcher: @@ -1481,38 +1383,27 @@ class ChildWatcherTestsMixin: self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # child terminates (signal 8) self.running = False - self.add_zombie(51, -8) + self.add_zombie(51, SIGNAL(8)) self.watcher._sig_chld() callback2.assert_called_once_with(51, -8, 21) self.assertFalse(callback1.called) - self.assertTrue(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertTrue(m.WTERMSIG.called) - m.WIFSIGNALED.reset_mock() - m.WIFEXITED.reset_mock() - m.WTERMSIG.reset_mock() callback2.reset_mock() # ensure that the child is effectively reaped - self.add_zombie(51, 13) + self.add_zombie(51, EXITCODE(13)) with self.ignore_warnings: self.watcher._sig_chld() self.assertFalse(callback1.called) self.assertFalse(callback2.called) - self.assertFalse(m.WTERMSIG.called) @waitpid_mocks - def test_sigchld_remove_handler(self, m): + def test_sigchld_remove_handler(self, m_waitpid): callback = mock.Mock() # register a child @@ -1521,30 +1412,22 @@ class ChildWatcherTestsMixin: self.watcher.add_child_handler(52, callback, 1984) self.assertFalse(callback.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # unregister the child self.watcher.remove_child_handler(52) self.assertFalse(callback.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # child terminates (code 99) self.running = False - self.add_zombie(52, 99) + self.add_zombie(52, EXITCODE(99)) with self.ignore_warnings: self.watcher._sig_chld() self.assertFalse(callback.called) @waitpid_mocks - def test_sigchld_unknown_status(self, m): + def test_sigchld_unknown_status(self, m_waitpid): callback = mock.Mock() # register a child @@ -1553,10 +1436,6 @@ class ChildWatcherTestsMixin: self.watcher.add_child_handler(53, callback, -19) self.assertFalse(callback.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # terminate with unknown status self.zombies[53] = 1178 @@ -1564,24 +1443,18 @@ class ChildWatcherTestsMixin: self.watcher._sig_chld() callback.assert_called_once_with(53, 1178, -19) - self.assertTrue(m.WIFEXITED.called) - self.assertTrue(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) callback.reset_mock() - m.WIFEXITED.reset_mock() - m.WIFSIGNALED.reset_mock() # ensure that the child is effectively reaped - self.add_zombie(53, 101) + self.add_zombie(53, EXITCODE(101)) with self.ignore_warnings: self.watcher._sig_chld() self.assertFalse(callback.called) @waitpid_mocks - def test_remove_child_handler(self, m): + def test_remove_child_handler(self, m_waitpid): callback1 = mock.Mock() callback2 = mock.Mock() callback3 = mock.Mock() @@ -1602,9 +1475,9 @@ class ChildWatcherTestsMixin: self.assertFalse(self.watcher.remove_child_handler(55)) # all children terminate - self.add_zombie(54, 0) - self.add_zombie(55, 1) - self.add_zombie(56, 2) + self.add_zombie(54, EXITCODE(0)) + self.add_zombie(55, EXITCODE(1)) + self.add_zombie(56, EXITCODE(2)) self.running = False with self.ignore_warnings: self.watcher._sig_chld() @@ -1614,7 +1487,7 @@ class ChildWatcherTestsMixin: callback3.assert_called_once_with(56, 2, 3) @waitpid_mocks - def test_sigchld_unhandled_exception(self, m): + def test_sigchld_unhandled_exception(self, m_waitpid): callback = mock.Mock() # register a child @@ -1623,7 +1496,7 @@ class ChildWatcherTestsMixin: self.watcher.add_child_handler(57, callback) # raise an exception - m.waitpid.side_effect = ValueError + m_waitpid.side_effect = ValueError with mock.patch.object(log.logger, 'error') as m_error: @@ -1632,7 +1505,7 @@ class ChildWatcherTestsMixin: self.assertTrue(m_error.called) @waitpid_mocks - def test_sigchld_child_reaped_elsewhere(self, m): + def test_sigchld_child_reaped_elsewhere(self, m_waitpid): # register a child callback = mock.Mock() @@ -1641,19 +1514,15 @@ class ChildWatcherTestsMixin: self.watcher.add_child_handler(58, callback) self.assertFalse(callback.called) - self.assertFalse(m.WIFEXITED.called) - self.assertFalse(m.WIFSIGNALED.called) - self.assertFalse(m.WEXITSTATUS.called) - self.assertFalse(m.WTERMSIG.called) # child terminates self.running = False - self.add_zombie(58, 4) + self.add_zombie(58, EXITCODE(4)) # waitpid is called elsewhere os.waitpid(58, os.WNOHANG) - m.waitpid.reset_mock() + m_waitpid.reset_mock() # sigchld with self.ignore_warnings: @@ -1667,7 +1536,7 @@ class ChildWatcherTestsMixin: callback.assert_called_once_with(58, 255) @waitpid_mocks - def test_sigchld_unknown_pid_during_registration(self, m): + def test_sigchld_unknown_pid_during_registration(self, m_waitpid): # register two children callback1 = mock.Mock() callback2 = mock.Mock() @@ -1675,9 +1544,9 @@ class ChildWatcherTestsMixin: with self.ignore_warnings, self.watcher: self.running = True # child 1 terminates - self.add_zombie(591, 7) + self.add_zombie(591, EXITCODE(7)) # an unknown child terminates - self.add_zombie(593, 17) + self.add_zombie(593, EXITCODE(17)) self.watcher._sig_chld() @@ -1688,7 +1557,7 @@ class ChildWatcherTestsMixin: self.assertFalse(callback2.called) @waitpid_mocks - def test_set_loop(self, m): + def test_set_loop(self, m_waitpid): # register a child callback = mock.Mock() @@ -1713,13 +1582,13 @@ class ChildWatcherTestsMixin: # child terminates self.running = False - self.add_zombie(60, 9) + self.add_zombie(60, EXITCODE(9)) self.watcher._sig_chld() callback.assert_called_once_with(60, 9) @waitpid_mocks - def test_set_loop_race_condition(self, m): + def test_set_loop_race_condition(self, m_waitpid): # register 3 children callback1 = mock.Mock() callback2 = mock.Mock() @@ -1746,8 +1615,8 @@ class ChildWatcherTestsMixin: signal.SIGCHLD) # child 1 & 2 terminate - self.add_zombie(61, 11) - self.add_zombie(62, -5) + self.add_zombie(61, EXITCODE(11)) + self.add_zombie(62, SIGNAL(5)) # SIGCHLD was not caught self.assertFalse(callback1.called) @@ -1773,7 +1642,7 @@ class ChildWatcherTestsMixin: # child 3 terminates self.running = False - self.add_zombie(622, 19) + self.add_zombie(622, EXITCODE(19)) self.watcher._sig_chld() self.assertFalse(callback1.called) @@ -1781,16 +1650,16 @@ class ChildWatcherTestsMixin: callback3.assert_called_once_with(622, 19) @waitpid_mocks - def test_close(self, m): + def test_close(self, m_waitpid): # register two children callback1 = mock.Mock() with self.watcher: self.running = True # child 1 terminates - self.add_zombie(63, 9) + self.add_zombie(63, EXITCODE(9)) # other child terminates - self.add_zombie(65, 18) + self.add_zombie(65, EXITCODE(18)) self.watcher._sig_chld() self.watcher.add_child_handler(63, callback1) diff --git a/Lib/test/test_asyncio/utils.py b/Lib/test/test_asyncio/utils.py index 34da7390e1..67180f7eb3 100644 --- a/Lib/test/test_asyncio/utils.py +++ b/Lib/test/test_asyncio/utils.py @@ -541,17 +541,10 @@ class TestCase(unittest.TestCase): self.set_event_loop(loop) return loop - def unpatch_get_running_loop(self): - events._get_running_loop = self._get_running_loop - def setUp(self): - self._get_running_loop = events._get_running_loop - events._get_running_loop = lambda: None self._thread_cleanup = threading_helper.threading_setup() def tearDown(self): - self.unpatch_get_running_loop() - events.set_event_loop(None) # Detect CPython bug #23353: ensure that yield/yield-from is not used diff --git a/Lib/test/test_asyncore.py b/Lib/test/test_asyncore.py index 06c6bc2e99..3bd904d177 100644 --- a/Lib/test/test_asyncore.py +++ b/Lib/test/test_asyncore.py @@ -69,7 +69,7 @@ def capture_server(evt, buf, serv): try: serv.listen() conn, addr = serv.accept() - except socket.timeout: + except TimeoutError: pass else: n = 200 diff --git a/Lib/test/test_atexit.py b/Lib/test/test_atexit.py index 3105f6c378..e0feef7c65 100644 --- a/Lib/test/test_atexit.py +++ b/Lib/test/test_atexit.py @@ -1,162 +1,22 @@ -import sys -import unittest -import io import atexit import os +import sys +import textwrap +import unittest from test import support from test.support import script_helper -### helpers -def h1(): - print("h1") - -def h2(): - print("h2") - -def h3(): - print("h3") - -def h4(*args, **kwargs): - print("h4", args, kwargs) - -def raise1(): - raise TypeError - -def raise2(): - raise SystemError - -def exit(): - raise SystemExit - class GeneralTest(unittest.TestCase): + def test_general(self): + # Run _test_atexit.py in a subprocess since it calls atexit._clear() + script = support.findfile("_test_atexit.py") + script_helper.run_test_script(script) - def setUp(self): - self.save_stdout = sys.stdout - self.save_stderr = sys.stderr - self.stream = io.StringIO() - sys.stdout = sys.stderr = self.stream - atexit._clear() - - def tearDown(self): - sys.stdout = self.save_stdout - sys.stderr = self.save_stderr - atexit._clear() - - def test_args(self): - # be sure args are handled properly - atexit.register(h1) - atexit.register(h4) - atexit.register(h4, 4, kw="abc") - atexit._run_exitfuncs() - - self.assertEqual(self.stream.getvalue(), - "h4 (4,) {'kw': 'abc'}\nh4 () {}\nh1\n") - - def test_badargs(self): - atexit.register(lambda: 1, 0, 0, (x for x in (1,2)), 0, 0) - self.assertRaises(TypeError, atexit._run_exitfuncs) - - def test_order(self): - # be sure handlers are executed in reverse order - atexit.register(h1) - atexit.register(h2) - atexit.register(h3) - atexit._run_exitfuncs() - - self.assertEqual(self.stream.getvalue(), "h3\nh2\nh1\n") - - def test_raise(self): - # be sure raises are handled properly - atexit.register(raise1) - atexit.register(raise2) - - self.assertRaises(TypeError, atexit._run_exitfuncs) - - def test_raise_unnormalized(self): - # Issue #10756: Make sure that an unnormalized exception is - # handled properly - atexit.register(lambda: 1 / 0) - - self.assertRaises(ZeroDivisionError, atexit._run_exitfuncs) - self.assertIn("ZeroDivisionError", self.stream.getvalue()) - - def test_exit(self): - # be sure a SystemExit is handled properly - atexit.register(exit) - - self.assertRaises(SystemExit, atexit._run_exitfuncs) - self.assertEqual(self.stream.getvalue(), '') - - def test_print_tracebacks(self): - # Issue #18776: the tracebacks should be printed when errors occur. - def f(): - 1/0 # one - def g(): - 1/0 # two - def h(): - 1/0 # three - atexit.register(f) - atexit.register(g) - atexit.register(h) - - self.assertRaises(ZeroDivisionError, atexit._run_exitfuncs) - stderr = self.stream.getvalue() - self.assertEqual(stderr.count("ZeroDivisionError"), 3) - self.assertIn("# one", stderr) - self.assertIn("# two", stderr) - self.assertIn("# three", stderr) - - def test_stress(self): - a = [0] - def inc(): - a[0] += 1 - - for i in range(128): - atexit.register(inc) - atexit._run_exitfuncs() - - self.assertEqual(a[0], 128) - - def test_clear(self): - a = [0] - def inc(): - a[0] += 1 - - atexit.register(inc) - atexit._clear() - atexit._run_exitfuncs() - - self.assertEqual(a[0], 0) - - def test_unregister(self): - a = [0] - def inc(): - a[0] += 1 - def dec(): - a[0] -= 1 - - for i in range(4): - atexit.register(inc) - atexit.register(dec) - atexit.unregister(inc) - atexit._run_exitfuncs() - - self.assertEqual(a[0], -1) - - def test_bound_methods(self): - l = [] - atexit.register(l.append, 5) - atexit._run_exitfuncs() - self.assertEqual(l, [5]) - - atexit.unregister(l.append) - atexit._run_exitfuncs() - self.assertEqual(l, [5]) - +class FunctionalTest(unittest.TestCase): def test_shutdown(self): # Actually test the shutdown mechanism in a subprocess - code = """if 1: + code = textwrap.dedent(""" import atexit def f(msg): @@ -164,11 +24,29 @@ class GeneralTest(unittest.TestCase): atexit.register(f, "one") atexit.register(f, "two") - """ + """) res = script_helper.assert_python_ok("-c", code) self.assertEqual(res.out.decode().splitlines(), ["two", "one"]) self.assertFalse(res.err) + def test_atexit_instances(self): + # bpo-42639: It is safe to have more than one atexit instance. + code = textwrap.dedent(""" + import sys + import atexit as atexit1 + del sys.modules['atexit'] + import atexit as atexit2 + del sys.modules['atexit'] + + assert atexit2 is not atexit1 + + atexit1.register(print, "atexit1") + atexit2.register(print, "atexit2") + """) + res = script_helper.assert_python_ok("-c", code) + self.assertEqual(res.out.decode().splitlines(), ["atexit2", "atexit1"]) + self.assertFalse(res.err) + @support.cpython_only class SubinterpreterTest(unittest.TestCase): @@ -178,13 +56,13 @@ class SubinterpreterTest(unittest.TestCase): # take care to free callbacks in its per-subinterpreter module # state. n = atexit._ncallbacks() - code = r"""if 1: + code = textwrap.dedent(r""" import atexit def f(): pass atexit.register(f) del atexit - """ + """) ret = support.run_in_subinterp(code) self.assertEqual(ret, 0) self.assertEqual(atexit._ncallbacks(), n) @@ -193,13 +71,13 @@ class SubinterpreterTest(unittest.TestCase): # Similar to the above, but with a refcycle through the atexit # module. n = atexit._ncallbacks() - code = r"""if 1: + code = textwrap.dedent(r""" import atexit def f(): pass atexit.register(f) atexit.__atexit = atexit - """ + """) ret = support.run_in_subinterp(code) self.assertEqual(ret, 0) self.assertEqual(atexit._ncallbacks(), n) @@ -210,13 +88,13 @@ class SubinterpreterTest(unittest.TestCase): expected = b"The test has passed!" r, w = os.pipe() - code = r"""if 1: + code = textwrap.dedent(r""" import os import atexit def callback(): os.write({:d}, b"The test has passed!") atexit.register(callback) - """.format(w) + """.format(w)) ret = support.run_in_subinterp(code) os.close(w) self.assertEqual(os.read(r, len(expected)), expected) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index edb4ec092e..8c9573731a 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -6,6 +6,7 @@ import builtins import collections import decimal import fractions +import gc import io import locale import os @@ -1756,6 +1757,18 @@ class BuiltinTest(unittest.TestCase): l8 = self.iter_error(zip(Iter(3), "AB", strict=True), ValueError) self.assertEqual(l8, [(2, "A"), (1, "B")]) + @support.cpython_only + def test_zip_result_gc(self): + # bpo-42536: zip's tuple-reuse speed trick breaks the GC's assumptions + # about what can be untracked. Make sure we re-track result tuples + # whenever we reuse them. + it = zip([[]]) + gc.collect() + # That GC collection probably untracked the recycled internal result + # tuple, which is initialized to (None,). Make sure it's re-tracked when + # it's mutated and returned from __next__: + self.assertTrue(gc.is_tracked(next(it))) + def test_format(self): # Test the basic machinery of the format() builtin. Don't test # the specifics of the various formatters diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index e61228d1a2..d550abfc65 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -1036,6 +1036,7 @@ class BytesTest(BaseBytesTest, unittest.TestCase): c_char_p) PyBytes_FromFormat = pythonapi.PyBytes_FromFormat + PyBytes_FromFormat.argtypes = (c_char_p,) PyBytes_FromFormat.restype = py_object # basic tests diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index db62b47100..a4ebe4a0a1 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -405,6 +405,9 @@ class CAPITest(unittest.TestCase): self.assertEqual(_testcapi.HeapDocCType.__doc__, "somedoc") self.assertEqual(_testcapi.HeapDocCType.__text_signature__, "(arg1, arg2)") + def test_null_type_doc(self): + self.assertEqual(_testcapi.NullTpDocType.__doc__, None) + def test_subclass_of_heap_gc_ctype_with_tpdealloc_decrefs_once(self): class HeapGcCTypeSubclass(_testcapi.HeapGcCType): def __init__(self): diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index 80b9aec7c2..4aa9691a48 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -795,17 +795,29 @@ class ClinicExternalTest(TestCase): maxDiff = None def test_external(self): + # bpo-42398: Test that the destination file is left unchanged if the + # content does not change. Moreover, check also that the file + # modification time does not change in this case. source = support.findfile('clinic.test') with open(source, 'r', encoding='utf-8') as f: - original = f.read() - with os_helper.temp_dir() as testdir: - testfile = os.path.join(testdir, 'clinic.test.c') + orig_contents = f.read() + + with os_helper.temp_dir() as tmp_dir: + testfile = os.path.join(tmp_dir, 'clinic.test.c') with open(testfile, 'w', encoding='utf-8') as f: - f.write(original) - clinic.parse_file(testfile, force=True) + f.write(orig_contents) + old_mtime_ns = os.stat(testfile).st_mtime_ns + + clinic.parse_file(testfile) + with open(testfile, 'r', encoding='utf-8') as f: - result = f.read() - self.assertEqual(result, original) + new_contents = f.read() + new_mtime_ns = os.stat(testfile).st_mtime_ns + + self.assertEqual(new_contents, orig_contents) + # Don't change the file modification time + # if the content does not change + self.assertEqual(new_mtime_ns, old_mtime_ns) if __name__ == "__main__": diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index fa3329efa2..f12dff3202 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -153,6 +153,14 @@ class CmdLineTest(unittest.TestCase): % (os_helper.FS_NONASCII, ord(os_helper.FS_NONASCII))) assert_python_ok('-c', command) + @unittest.skipUnless(os_helper.FS_NONASCII, 'need os_helper.FS_NONASCII') + def test_coding(self): + # bpo-32381: the -c command ignores the coding cookie + ch = os_helper.FS_NONASCII + cmd = f"# coding: latin1\nprint(ascii('{ch}'))" + res = assert_python_ok('-c', cmd) + self.assertEqual(res.out.rstrip(), ascii(ch).encode('ascii')) + # On Windows, pass bytes to subprocess doesn't test how Python decodes the # command line, but how subprocess does decode bytes to unicode. Python # doesn't decode the command line because Windows provides directly the diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index ac3dde7456..467e8e5e33 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -258,7 +258,7 @@ class CodeTest(unittest.TestCase): ("co_cellvars", ("cellvar",)), ("co_filename", "newfilename"), ("co_name", "newname"), - ("co_lnotab", code2.co_lnotab), + ("co_linetable", code2.co_linetable), ): with self.subTest(attr=attr, value=value): new_code = code.replace(**{attr: value}) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 7c7f8655b0..a1ca958257 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -196,6 +196,22 @@ class TestChainMap(unittest.TestCase): ('e', 55), ('f', 666), ('g', 777), ('h', 88888), ('i', 9999), ('j', 0)]) + def test_iter_not_calling_getitem_on_maps(self): + class DictWithGetItem(UserDict): + def __init__(self, *args, **kwds): + self.called = False + UserDict.__init__(self, *args, **kwds) + def __getitem__(self, item): + self.called = True + UserDict.__getitem__(self, item) + + d = DictWithGetItem(a=1) + c = ChainMap(d) + d.called = False + + set(c) # iterate over chain map + self.assertFalse(d.called, '__getitem__ was called') + def test_dict_coercion(self): d = ChainMap(dict(a=1, b=2), dict(b=20, c=30)) self.assertEqual(dict(d), dict(a=1, b=2, c=30)) @@ -1559,6 +1575,62 @@ class TestCollectionABCs(ABCTestCase): # coerce both to a real set then check equality self.assertSetEqual(set(s1), set(s2)) + def test_Set_from_iterable(self): + """Verify _from_iterable overriden to an instance method works.""" + class SetUsingInstanceFromIterable(MutableSet): + def __init__(self, values, created_by): + if not created_by: + raise ValueError(f'created_by must be specified') + self.created_by = created_by + self._values = set(values) + + def _from_iterable(self, values): + return type(self)(values, 'from_iterable') + + def __contains__(self, value): + return value in self._values + + def __iter__(self): + yield from self._values + + def __len__(self): + return len(self._values) + + def add(self, value): + self._values.add(value) + + def discard(self, value): + self._values.discard(value) + + impl = SetUsingInstanceFromIterable([1, 2, 3], 'test') + + actual = impl - {1} + self.assertIsInstance(actual, SetUsingInstanceFromIterable) + self.assertEqual('from_iterable', actual.created_by) + self.assertEqual({2, 3}, actual) + + actual = impl | {4} + self.assertIsInstance(actual, SetUsingInstanceFromIterable) + self.assertEqual('from_iterable', actual.created_by) + self.assertEqual({1, 2, 3, 4}, actual) + + actual = impl & {2} + self.assertIsInstance(actual, SetUsingInstanceFromIterable) + self.assertEqual('from_iterable', actual.created_by) + self.assertEqual({2}, actual) + + actual = impl ^ {3, 4} + self.assertIsInstance(actual, SetUsingInstanceFromIterable) + self.assertEqual('from_iterable', actual.created_by) + self.assertEqual({1, 2, 4}, actual) + + # NOTE: ixor'ing with a list is important here: internally, __ixor__ + # only calls _from_iterable if the other value isn't already a Set. + impl ^= [3, 4] + self.assertIsInstance(impl, SetUsingInstanceFromIterable) + self.assertEqual('test', impl.created_by) + self.assertEqual({1, 2, 4}, impl) + def test_Set_interoperability_with_real_sets(self): # Issue: 8743 class ListSet(Set): diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 6055192bf7..3e826b9acc 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -155,8 +155,8 @@ if 1: def test_leading_newlines(self): s256 = "".join(["\n"] * 256 + ["spam"]) co = compile(s256, 'fn', 'exec') - self.assertEqual(co.co_firstlineno, 257) - self.assertEqual(co.co_lnotab, bytes()) + self.assertEqual(co.co_firstlineno, 1) + self.assertEqual(list(co.co_lines()), [(0, 8, 257)]) def test_literals_with_leading_zeroes(self): for arg in ["077787", "0xj", "0x.", "0e", "090000000000000", @@ -728,10 +728,10 @@ if 1: for func in funcs: opcodes = list(dis.get_instructions(func)) - self.assertEqual(2, len(opcodes)) - self.assertEqual('LOAD_CONST', opcodes[0].opname) - self.assertEqual(None, opcodes[0].argval) - self.assertEqual('RETURN_VALUE', opcodes[1].opname) + self.assertLessEqual(len(opcodes), 3) + self.assertEqual('LOAD_CONST', opcodes[-2].opname) + self.assertEqual(None, opcodes[-2].argval) + self.assertEqual('RETURN_VALUE', opcodes[-1].opname) def test_false_while_loop(self): def break_in_while(): @@ -752,6 +752,81 @@ if 1: self.assertEqual(None, opcodes[0].argval) self.assertEqual('RETURN_VALUE', opcodes[1].opname) + def test_consts_in_conditionals(self): + def and_true(x): + return True and x + + def and_false(x): + return False and x + + def or_true(x): + return True or x + + def or_false(x): + return False or x + + funcs = [and_true, and_false, or_true, or_false] + + # Check that condition is removed. + for func in funcs: + with self.subTest(func=func): + opcodes = list(dis.get_instructions(func)) + self.assertEqual(2, len(opcodes)) + self.assertIn('LOAD_', opcodes[0].opname) + self.assertEqual('RETURN_VALUE', opcodes[1].opname) + + def test_lineno_after_implicit_return(self): + TRUE = True + # Don't use constant True or False, as compiler will remove test + def if1(x): + x() + if TRUE: + pass + def if2(x): + x() + if TRUE: + pass + else: + pass + def if3(x): + x() + if TRUE: + pass + else: + return None + def if4(x): + x() + if not TRUE: + pass + funcs = [ if1, if2, if3, if4] + lastlines = [ 3, 3, 3, 2] + frame = None + def save_caller_frame(): + nonlocal frame + frame = sys._getframe(1) + for func, lastline in zip(funcs, lastlines, strict=True): + with self.subTest(func=func): + func(save_caller_frame) + self.assertEqual(frame.f_lineno-frame.f_code.co_firstlineno, lastline) + + def test_lineno_after_no_code(self): + def no_code1(): + "doc string" + + def no_code2(): + a: int + + for func in (no_code1, no_code2): + with self.subTest(func=func): + code = func.__code__ + lines = list(code.co_lines()) + self.assertEqual(len(lines), 1) + start, end, line = lines[0] + self.assertEqual(start, 0) + self.assertEqual(end, len(code.co_code)) + self.assertEqual(line, code.co_firstlineno) + + def test_big_dict_literal(self): # The compiler has a flushing point in "compiler_dict" that calls compiles # a portion of the dictionary literal when the loop that iterates over the items @@ -762,6 +837,34 @@ if 1: the_dict = "{" + ",".join(f"{x}:{x}" for x in range(dict_size)) + "}" self.assertEqual(len(eval(the_dict)), dict_size) + def test_redundant_jump_in_if_else_break(self): + # Check if bytecode containing jumps that simply point to the next line + # is generated around if-else-break style structures. See bpo-42615. + + def if_else_break(): + val = 1 + while True: + if val > 0: + val -= 1 + else: + break + val = -1 + + INSTR_SIZE = 2 + HANDLED_JUMPS = ( + 'POP_JUMP_IF_FALSE', + 'POP_JUMP_IF_TRUE', + 'JUMP_ABSOLUTE', + 'JUMP_FORWARD', + ) + + for line, instr in enumerate(dis.Bytecode(if_else_break)): + if instr.opname == 'JUMP_FORWARD': + self.assertNotEqual(instr.arg, 0) + elif instr.opname in HANDLED_JUMPS: + self.assertNotEqual(instr.arg, (line + 1)*INSTR_SIZE) + + class TestExpressionStackSize(unittest.TestCase): # These tests check that the computed stack size for a code object # stays within reasonable bounds (see issue #21523 for an example diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index 3765f6cbf2..290ef05b82 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -1,5 +1,7 @@ import asyncio -from contextlib import aclosing, asynccontextmanager, AbstractAsyncContextManager, AsyncExitStack +from contextlib import ( + asynccontextmanager, AbstractAsyncContextManager, + AsyncExitStack, nullcontext, aclosing) import functools from test import support import unittest @@ -278,6 +280,33 @@ class AsyncContextManagerTestCase(unittest.TestCase): async with woohoo(self=11, func=22, args=33, kwds=44) as target: self.assertEqual(target, (11, 22, 33, 44)) + @_async_test + async def test_recursive(self): + depth = 0 + ncols = 0 + + @asynccontextmanager + async def woohoo(): + nonlocal ncols + ncols += 1 + + nonlocal depth + before = depth + depth += 1 + yield + depth -= 1 + self.assertEqual(depth, before) + + @woohoo() + async def recursive(): + if depth < 10: + await recursive() + + await recursive() + + self.assertEqual(ncols, 10) + self.assertEqual(depth, 0) + class AclosingTestCase(unittest.TestCase): @@ -510,5 +539,15 @@ class TestAsyncExitStack(TestBaseExitStack, unittest.TestCase): self.assertIsInstance(inner_exc.__context__, ZeroDivisionError) +class TestAsyncNullcontext(unittest.TestCase): + @_async_test + async def test_async_nullcontext(self): + class C: + pass + c = C() + async with nullcontext(c) as c_in: + self.assertIs(c_in, c) + + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 307416c330..f0048f42f8 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4,6 +4,8 @@ import gc import itertools import math import pickle +import random +import string import sys import types import unittest @@ -845,6 +847,14 @@ class ClassPropertiesAndMethods(unittest.TestCase): self.fail("inheriting from ModuleType and str at the same time " "should fail") + # Issue 34805: Verify that definition order is retained + def random_name(): + return ''.join(random.choices(string.ascii_letters, k=10)) + class A: + pass + subclasses = [type(random_name(), (A,), {}) for i in range(100)] + self.assertEqual(A.__subclasses__(), subclasses) + def test_multiple_inheritance(self): # Testing multiple inheritance... class C(object): diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 9ff8b7d501..4b31cdc794 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1452,6 +1452,25 @@ class DictTest(unittest.TestCase): d = CustomReversedDict(pairs) self.assertEqual(pairs[::-1], list(dict(d).items())) + @support.cpython_only + def test_dict_items_result_gc(self): + # bpo-42536: dict.items's tuple-reuse speed trick breaks the GC's + # assumptions about what can be untracked. Make sure we re-track result + # tuples whenever we reuse them. + it = iter({None: []}.items()) + gc.collect() + # That GC collection probably untracked the recycled internal result + # tuple, which is initialized to (None, None). Make sure it's re-tracked + # when it's mutated and returned from __next__: + self.assertTrue(gc.is_tracked(next(it))) + + @support.cpython_only + def test_dict_items_result_gc(self): + # Same as test_dict_items_result_gc above, but reversed. + it = reversed({None: []}.items()) + gc.collect() + self.assertTrue(gc.is_tracked(next(it))) + class CAPITest(unittest.TestCase): diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index bbaddd57d2..0383124464 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -131,12 +131,14 @@ dis_bug708901 = """\ 12 STORE_FAST 0 (res) %3d 14 JUMP_ABSOLUTE 10 - >> 16 LOAD_CONST 0 (None) + +%3d >> 16 LOAD_CONST 0 (None) 18 RETURN_VALUE """ % (bug708901.__code__.co_firstlineno + 1, bug708901.__code__.co_firstlineno + 2, bug708901.__code__.co_firstlineno + 1, - bug708901.__code__.co_firstlineno + 3) + bug708901.__code__.co_firstlineno + 3, + bug708901.__code__.co_firstlineno + 1) def bug1333982(x=[]): @@ -145,30 +147,38 @@ def bug1333982(x=[]): pass dis_bug1333982 = """\ -%3d 0 LOAD_CONST 1 (0) - 2 POP_JUMP_IF_TRUE 26 - 4 LOAD_ASSERTION_ERROR - 6 LOAD_CONST 2 (<code object <listcomp> at 0x..., file "%s", line %d>) - 8 LOAD_CONST 3 ('bug1333982.<locals>.<listcomp>') - 10 MAKE_FUNCTION 0 - 12 LOAD_FAST 0 (x) - 14 GET_ITER - 16 CALL_FUNCTION 1 - -%3d 18 LOAD_CONST 4 (1) - -%3d 20 BINARY_ADD - 22 CALL_FUNCTION 1 - 24 RAISE_VARARGS 1 - -%3d >> 26 LOAD_CONST 0 (None) - 28 RETURN_VALUE +%3d 0 LOAD_ASSERTION_ERROR + 2 LOAD_CONST 2 (<code object <listcomp> at 0x..., file "%s", line %d>) + 4 LOAD_CONST 3 ('bug1333982.<locals>.<listcomp>') + 6 MAKE_FUNCTION 0 + 8 LOAD_FAST 0 (x) + 10 GET_ITER + 12 CALL_FUNCTION 1 + +%3d 14 LOAD_CONST 4 (1) + +%3d 16 BINARY_ADD + 18 CALL_FUNCTION 1 + 20 RAISE_VARARGS 1 """ % (bug1333982.__code__.co_firstlineno + 1, __file__, bug1333982.__code__.co_firstlineno + 1, bug1333982.__code__.co_firstlineno + 2, - bug1333982.__code__.co_firstlineno + 1, - bug1333982.__code__.co_firstlineno + 3) + bug1333982.__code__.co_firstlineno + 1) + + +def bug42562(): + pass + + +# Set line number for 'pass' to None +bug42562.__code__ = bug42562.__code__.replace(co_linetable=b'\x04\x80\xff\x80') + + +dis_bug42562 = """\ + 0 LOAD_CONST 0 (None) + 2 RETURN_VALUE +""" _BIG_LINENO_FORMAT = """\ %3d 0 LOAD_GLOBAL 0 (spam) @@ -259,55 +269,60 @@ dis_compound_stmt_str = """\ 1 0 LOAD_CONST 0 (0) 2 STORE_NAME 0 (x) - 3 >> 4 LOAD_NAME 0 (x) - 6 LOAD_CONST 1 (1) - 8 INPLACE_ADD - 10 STORE_NAME 0 (x) - 12 JUMP_ABSOLUTE 4 - 14 LOAD_CONST 2 (None) - 16 RETURN_VALUE + 2 4 NOP + + 3 >> 6 LOAD_NAME 0 (x) + 8 LOAD_CONST 1 (1) + 10 INPLACE_ADD + 12 STORE_NAME 0 (x) + + 2 14 JUMP_ABSOLUTE 6 """ dis_traceback = """\ -%3d 0 SETUP_FINALLY 12 (to 14) +%3d 0 SETUP_FINALLY 14 (to 16) %3d 2 LOAD_CONST 1 (1) 4 LOAD_CONST 2 (0) --> 6 BINARY_TRUE_DIVIDE 8 POP_TOP 10 POP_BLOCK - 12 JUMP_FORWARD 42 (to 56) - -%3d >> 14 DUP_TOP - 16 LOAD_GLOBAL 0 (Exception) - 18 JUMP_IF_NOT_EXC_MATCH 54 - 20 POP_TOP - 22 STORE_FAST 0 (e) - 24 POP_TOP - 26 SETUP_FINALLY 18 (to 46) - -%3d 28 LOAD_FAST 0 (e) - 30 LOAD_ATTR 1 (__traceback__) - 32 STORE_FAST 1 (tb) - 34 POP_BLOCK - 36 POP_EXCEPT - 38 LOAD_CONST 0 (None) - 40 STORE_FAST 0 (e) - 42 DELETE_FAST 0 (e) - 44 JUMP_FORWARD 10 (to 56) - >> 46 LOAD_CONST 0 (None) - 48 STORE_FAST 0 (e) - 50 DELETE_FAST 0 (e) - 52 RERAISE - >> 54 RERAISE - -%3d >> 56 LOAD_FAST 1 (tb) - 58 RETURN_VALUE + +%3d 12 LOAD_FAST 1 (tb) + 14 RETURN_VALUE + +%3d >> 16 DUP_TOP + 18 LOAD_GLOBAL 0 (Exception) + 20 JUMP_IF_NOT_EXC_MATCH 58 + 22 POP_TOP + 24 STORE_FAST 0 (e) + 26 POP_TOP + 28 SETUP_FINALLY 20 (to 50) + +%3d 30 LOAD_FAST 0 (e) + 32 LOAD_ATTR 1 (__traceback__) + 34 STORE_FAST 1 (tb) + 36 POP_BLOCK + 38 POP_EXCEPT + 40 LOAD_CONST 0 (None) + 42 STORE_FAST 0 (e) + 44 DELETE_FAST 0 (e) + +%3d 46 LOAD_FAST 1 (tb) + 48 RETURN_VALUE + >> 50 LOAD_CONST 0 (None) + 52 STORE_FAST 0 (e) + 54 DELETE_FAST 0 (e) + 56 RERAISE 1 + +%3d >> 58 RERAISE 0 """ % (TRACEBACK_CODE.co_firstlineno + 1, TRACEBACK_CODE.co_firstlineno + 2, + TRACEBACK_CODE.co_firstlineno + 5, TRACEBACK_CODE.co_firstlineno + 3, TRACEBACK_CODE.co_firstlineno + 4, - TRACEBACK_CODE.co_firstlineno + 5) + TRACEBACK_CODE.co_firstlineno + 5, + TRACEBACK_CODE.co_firstlineno + 3) def _fstring(a, b, c, d): return f'{a} {b:4} {c!r} {d!r:4}' @@ -351,18 +366,14 @@ dis_tryfinally = """\ %3d 6 LOAD_FAST 1 (b) 8 CALL_FUNCTION 0 10 POP_TOP - -%3d 12 RETURN_VALUE - -%3d >> 14 LOAD_FAST 1 (b) + 12 RETURN_VALUE + >> 14 LOAD_FAST 1 (b) 16 CALL_FUNCTION 0 18 POP_TOP - 20 RERAISE + 20 RERAISE 0 """ % (_tryfinally.__code__.co_firstlineno + 1, _tryfinally.__code__.co_firstlineno + 2, _tryfinally.__code__.co_firstlineno + 4, - _tryfinally.__code__.co_firstlineno + 2, - _tryfinally.__code__.co_firstlineno + 4, ) dis_tryfinallyconst = """\ @@ -373,19 +384,15 @@ dis_tryfinallyconst = """\ %3d 4 LOAD_FAST 0 (b) 6 CALL_FUNCTION 0 8 POP_TOP - -%3d 10 LOAD_CONST 1 (1) + 10 LOAD_CONST 1 (1) 12 RETURN_VALUE - -%3d >> 14 LOAD_FAST 0 (b) + >> 14 LOAD_FAST 0 (b) 16 CALL_FUNCTION 0 18 POP_TOP - 20 RERAISE + 20 RERAISE 0 """ % (_tryfinallyconst.__code__.co_firstlineno + 1, _tryfinallyconst.__code__.co_firstlineno + 2, _tryfinallyconst.__code__.co_firstlineno + 4, - _tryfinallyconst.__code__.co_firstlineno + 2, - _tryfinallyconst.__code__.co_firstlineno + 4, ) def _g(x): @@ -522,6 +529,9 @@ class DisTests(unittest.TestCase): self.do_disassembly_test(bug1333982, dis_bug1333982) + def test_bug_42562(self): + self.do_disassembly_test(bug42562, dis_bug42562) + def test_big_linenos(self): def func(count): namespace = {} @@ -674,8 +684,15 @@ class DisWithFileTests(DisTests): return output.getvalue() +if sys.flags.optimize: + code_info_consts = "0: None" +else: + code_info_consts = ( + """0: 'Formatted details of methods, functions, or code.' + 1: None""" +) -code_info_code_info = """\ +code_info_code_info = f"""\ Name: code_info Filename: (.*) Argument count: 1 @@ -685,13 +702,13 @@ Number of locals: 1 Stack size: 3 Flags: OPTIMIZED, NEWLOCALS, NOFREE Constants: - 0: %r + {code_info_consts} Names: 0: _format_code_info 1: _get_code_object Variable names: - 0: x""" % (('Formatted details of methods, functions, or code.',) - if sys.flags.optimize < 2 else (None,)) + 0: x""" + @staticmethod def tricky(a, b, /, x, y, z=True, *args, c, d, e=[], **kwds): @@ -1006,17 +1023,17 @@ expected_opinfo_jumpy = [ Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=30, starts_line=7, is_jump_target=True), Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=6, argrepr='6', offset=32, starts_line=None, is_jump_target=False), Instruction(opname='COMPARE_OP', opcode=107, arg=4, argval='>', argrepr='>', offset=34, starts_line=None, is_jump_target=False), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=8, argval=8, argrepr='', offset=36, starts_line=None, is_jump_target=False), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=42, argval=42, argrepr='', offset=36, starts_line=None, is_jump_target=False), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=38, starts_line=8, is_jump_target=False), Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=52, argval=52, argrepr='', offset=40, starts_line=None, is_jump_target=False), - Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=8, argval=8, argrepr='', offset=42, starts_line=None, is_jump_target=False), + Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=8, argval=8, argrepr='', offset=42, starts_line=None, is_jump_target=True), Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=44, starts_line=10, is_jump_target=True), Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=46, starts_line=None, is_jump_target=False), Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=48, starts_line=None, is_jump_target=False), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=50, starts_line=None, is_jump_target=False), Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=52, starts_line=11, is_jump_target=True), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=94, argval=94, argrepr='', offset=54, starts_line=None, is_jump_target=False), - Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=56, starts_line=12, is_jump_target=False), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=96, argval=96, argrepr='', offset=54, starts_line=None, is_jump_target=False), + Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=56, starts_line=12, is_jump_target=True), Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=58, starts_line=None, is_jump_target=False), Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=60, starts_line=None, is_jump_target=False), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=62, starts_line=None, is_jump_target=False), @@ -1032,34 +1049,34 @@ expected_opinfo_jumpy = [ Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=82, starts_line=16, is_jump_target=True), Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=4, argrepr='4', offset=84, starts_line=None, is_jump_target=False), Instruction(opname='COMPARE_OP', opcode=107, arg=0, argval='<', argrepr='<', offset=86, starts_line=None, is_jump_target=False), - Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=52, argval=52, argrepr='', offset=88, starts_line=None, is_jump_target=False), - Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=102, argval=102, argrepr='', offset=90, starts_line=17, is_jump_target=False), - Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=52, argval=52, argrepr='', offset=92, starts_line=None, is_jump_target=False), - Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=94, starts_line=19, is_jump_target=True), - Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=96, starts_line=None, is_jump_target=False), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=98, starts_line=None, is_jump_target=False), - Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=100, starts_line=None, is_jump_target=False), - Instruction(opname='SETUP_FINALLY', opcode=122, arg=96, argval=200, argrepr='to 200', offset=102, starts_line=20, is_jump_target=True), - Instruction(opname='SETUP_FINALLY', opcode=122, arg=12, argval=118, argrepr='to 118', offset=104, starts_line=None, is_jump_target=False), - Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=106, starts_line=21, is_jump_target=False), - Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval=0, argrepr='0', offset=108, starts_line=None, is_jump_target=False), - Instruction(opname='BINARY_TRUE_DIVIDE', opcode=27, arg=None, argval=None, argrepr='', offset=110, starts_line=None, is_jump_target=False), - Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=112, starts_line=None, is_jump_target=False), - Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=114, starts_line=None, is_jump_target=False), - Instruction(opname='JUMP_FORWARD', opcode=110, arg=26, argval=144, argrepr='to 144', offset=116, starts_line=None, is_jump_target=False), - Instruction(opname='DUP_TOP', opcode=4, arg=None, argval=None, argrepr='', offset=118, starts_line=22, is_jump_target=True), - Instruction(opname='LOAD_GLOBAL', opcode=116, arg=2, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=120, starts_line=None, is_jump_target=False), - Instruction(opname='JUMP_IF_NOT_EXC_MATCH', opcode=121, arg=142, argval=142, argrepr='', offset=122, starts_line=None, is_jump_target=False), - Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=124, starts_line=None, is_jump_target=False), + Instruction(opname='POP_JUMP_IF_FALSE', opcode=114, arg=92, argval=92, argrepr='', offset=88, starts_line=None, is_jump_target=False), + Instruction(opname='JUMP_ABSOLUTE', opcode=113, arg=104, argval=104, argrepr='', offset=90, starts_line=17, is_jump_target=False), + Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=92, starts_line=11, is_jump_target=True), + Instruction(opname='POP_JUMP_IF_TRUE', opcode=115, arg=56, argval=56, argrepr='', offset=94, starts_line=None, is_jump_target=False), + Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=96, starts_line=19, is_jump_target=True), + Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval='Who let lolcatz into this test suite?', argrepr="'Who let lolcatz into this test suite?'", offset=98, starts_line=None, is_jump_target=False), + Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=100, starts_line=None, is_jump_target=False), + Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=102, starts_line=None, is_jump_target=False), + Instruction(opname='SETUP_FINALLY', opcode=122, arg=96, argval=202, argrepr='to 202', offset=104, starts_line=20, is_jump_target=True), + Instruction(opname='SETUP_FINALLY', opcode=122, arg=12, argval=120, argrepr='to 120', offset=106, starts_line=None, is_jump_target=False), + Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=108, starts_line=21, is_jump_target=False), + Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval=0, argrepr='0', offset=110, starts_line=None, is_jump_target=False), + Instruction(opname='BINARY_TRUE_DIVIDE', opcode=27, arg=None, argval=None, argrepr='', offset=112, starts_line=None, is_jump_target=False), + Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=114, starts_line=None, is_jump_target=False), + Instruction(opname='POP_BLOCK', opcode=87, arg=None, argval=None, argrepr='', offset=116, starts_line=None, is_jump_target=False), + Instruction(opname='JUMP_FORWARD', opcode=110, arg=24, argval=144, argrepr='to 144', offset=118, starts_line=None, is_jump_target=False), + Instruction(opname='DUP_TOP', opcode=4, arg=None, argval=None, argrepr='', offset=120, starts_line=22, is_jump_target=True), + Instruction(opname='LOAD_GLOBAL', opcode=116, arg=2, argval='ZeroDivisionError', argrepr='ZeroDivisionError', offset=122, starts_line=None, is_jump_target=False), + Instruction(opname='JUMP_IF_NOT_EXC_MATCH', opcode=121, arg=212, argval=212, argrepr='', offset=124, starts_line=None, is_jump_target=False), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=126, starts_line=None, is_jump_target=False), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=128, starts_line=None, is_jump_target=False), - Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=130, starts_line=23, is_jump_target=False), - Instruction(opname='LOAD_CONST', opcode=100, arg=8, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=132, starts_line=None, is_jump_target=False), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=134, starts_line=None, is_jump_target=False), - Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=136, starts_line=None, is_jump_target=False), - Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=138, starts_line=None, is_jump_target=False), - Instruction(opname='JUMP_FORWARD', opcode=110, arg=46, argval=188, argrepr='to 188', offset=140, starts_line=None, is_jump_target=False), - Instruction(opname='RERAISE', opcode=48, arg=None, argval=None, argrepr='', offset=142, starts_line=None, is_jump_target=True), + Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=130, starts_line=None, is_jump_target=False), + Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=132, starts_line=23, is_jump_target=False), + Instruction(opname='LOAD_CONST', opcode=100, arg=8, argval='Here we go, here we go, here we go...', argrepr="'Here we go, here we go, here we go...'", offset=134, starts_line=None, is_jump_target=False), + Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=136, starts_line=None, is_jump_target=False), + Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=138, starts_line=None, is_jump_target=False), + Instruction(opname='POP_EXCEPT', opcode=89, arg=None, argval=None, argrepr='', offset=140, starts_line=None, is_jump_target=False), + Instruction(opname='JUMP_FORWARD', opcode=110, arg=44, argval=188, argrepr='to 188', offset=142, starts_line=None, is_jump_target=False), Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=144, starts_line=25, is_jump_target=True), Instruction(opname='SETUP_WITH', opcode=143, arg=24, argval=172, argrepr='to 172', offset=146, starts_line=None, is_jump_target=False), Instruction(opname='STORE_FAST', opcode=125, arg=1, argval='dodgy', argrepr='dodgy', offset=148, starts_line=None, is_jump_target=False), @@ -1076,7 +1093,7 @@ expected_opinfo_jumpy = [ Instruction(opname='JUMP_FORWARD', opcode=110, arg=16, argval=188, argrepr='to 188', offset=170, starts_line=None, is_jump_target=False), Instruction(opname='WITH_EXCEPT_START', opcode=49, arg=None, argval=None, argrepr='', offset=172, starts_line=None, is_jump_target=True), Instruction(opname='POP_JUMP_IF_TRUE', opcode=115, arg=178, argval=178, argrepr='', offset=174, starts_line=None, is_jump_target=False), - Instruction(opname='RERAISE', opcode=48, arg=None, argval=None, argrepr='', offset=176, starts_line=None, is_jump_target=False), + Instruction(opname='RERAISE', opcode=119, arg=1, argval=1, argrepr='', offset=176, starts_line=None, is_jump_target=False), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=178, starts_line=None, is_jump_target=True), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=180, starts_line=None, is_jump_target=False), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=182, starts_line=None, is_jump_target=False), @@ -1087,14 +1104,14 @@ expected_opinfo_jumpy = [ Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=192, starts_line=None, is_jump_target=False), Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=194, starts_line=None, is_jump_target=False), Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=196, starts_line=None, is_jump_target=False), - Instruction(opname='JUMP_FORWARD', opcode=110, arg=10, argval=210, argrepr='to 210', offset=198, starts_line=None, is_jump_target=False), - Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=200, starts_line=None, is_jump_target=True), - Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=202, starts_line=None, is_jump_target=False), - Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=204, starts_line=None, is_jump_target=False), - Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=206, starts_line=None, is_jump_target=False), - Instruction(opname='RERAISE', opcode=48, arg=None, argval=None, argrepr='', offset=208, starts_line=None, is_jump_target=False), - Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=210, starts_line=None, is_jump_target=True), - Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=212, starts_line=None, is_jump_target=False), + Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=198, starts_line=None, is_jump_target=False), + Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=200, starts_line=None, is_jump_target=False), + Instruction(opname='LOAD_GLOBAL', opcode=116, arg=1, argval='print', argrepr='print', offset=202, starts_line=None, is_jump_target=True), + Instruction(opname='LOAD_CONST', opcode=100, arg=10, argval="OK, now we're done", argrepr='"OK, now we\'re done"', offset=204, starts_line=None, is_jump_target=False), + Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='', offset=206, starts_line=None, is_jump_target=False), + Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=208, starts_line=None, is_jump_target=False), + Instruction(opname='RERAISE', opcode=119, arg=0, argval=0, argrepr='', offset=210, starts_line=None, is_jump_target=False), + Instruction(opname='RERAISE', opcode=119, arg=0, argval=0, argrepr='', offset=212, starts_line=22, is_jump_target=True), ] # One last piece of inspect fodder to check the default line number handling @@ -1196,5 +1213,24 @@ class BytecodeTests(unittest.TestCase): b = dis.Bytecode.from_traceback(tb) self.assertEqual(b.dis(), dis_traceback) + +class TestBytecodeTestCase(BytecodeTestCase): + def test_assert_not_in_with_op_not_in_bytecode(self): + code = compile("a = 1", "<string>", "exec") + self.assertInBytecode(code, "LOAD_CONST", 1) + self.assertNotInBytecode(code, "LOAD_NAME") + self.assertNotInBytecode(code, "LOAD_NAME", "a") + + def test_assert_not_in_with_arg_not_in_bytecode(self): + code = compile("a = 1", "<string>", "exec") + self.assertInBytecode(code, "LOAD_CONST") + self.assertInBytecode(code, "LOAD_CONST", 1) + self.assertNotInBytecode(code, "LOAD_CONST", 2) + + def test_assert_not_in_with_arg_in_bytecode(self): + code = compile("a = 1", "<string>", "exec") + with self.assertRaises(AssertionError): + self.assertNotInBytecode(code, "LOAD_CONST", 1) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index bff20f9cac..6a5013f5b8 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -3039,10 +3039,11 @@ Invalid file name: ... '-m', 'doctest', 'nosuchfile') >>> rc, out (1, b'') + >>> # The exact error message changes depending on the platform. >>> print(normalize(err)) # doctest: +ELLIPSIS Traceback (most recent call last): ... - FileNotFoundError: [Errno ...] No such file or directory: 'nosuchfile' + FileNotFoundError: [Errno ...] ...nosuchfile... Invalid doctest option: diff --git a/Lib/test/test_eintr.py b/Lib/test/test_eintr.py index a5f8f6465e..528147802b 100644 --- a/Lib/test/test_eintr.py +++ b/Lib/test/test_eintr.py @@ -1,9 +1,6 @@ import os import signal -import subprocess -import sys import unittest - from test import support from test.support import script_helper @@ -15,22 +12,8 @@ class EINTRTests(unittest.TestCase): def test_all(self): # Run the tester in a sub-process, to make sure there is only one # thread (for reliable signal delivery). - tester = support.findfile("eintr_tester.py", subdir="eintrdata") - # use -u to try to get the full output if the test hangs or crash - args = ["-u", tester, "-v"] - if support.verbose: - print() - print("--- run eintr_tester.py ---", flush=True) - # In verbose mode, the child process inherit stdout and stdout, - # to see output in realtime and reduce the risk of losing output. - args = [sys.executable, "-E", "-X", "faulthandler", *args] - proc = subprocess.run(args) - print(f"--- eintr_tester.py completed: " - f"exit code {proc.returncode} ---", flush=True) - if proc.returncode: - self.fail("eintr_tester.py failed") - else: - script_helper.assert_python_ok("-u", tester, "-v") + script = support.findfile("_test_eintr.py") + script_helper.run_test_script(script) if __name__ == "__main__": diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 31dc39fd9e..a7d912178a 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -30,6 +30,8 @@ API_PYTHON = 2 # _PyCoreConfig_InitIsolatedConfig() API_ISOLATED = 3 +MAX_HASH_SEED = 4294967295 + def debug_build(program): program = os.path.basename(program) @@ -382,6 +384,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'exec_prefix': GET_DEFAULT_CONFIG, 'base_exec_prefix': GET_DEFAULT_CONFIG, 'module_search_paths': GET_DEFAULT_CONFIG, + 'module_search_paths_set': 1, 'platlibdir': sys.platlibdir, 'site_import': 1, @@ -419,7 +422,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): CONFIG_PYTHON = dict(CONFIG_COMPAT, _config_init=API_PYTHON, configure_c_stdio=1, - parse_argv=1, + parse_argv=2, ) CONFIG_ISOLATED = dict(CONFIG_COMPAT, _config_init=API_ISOLATED, @@ -797,7 +800,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): '-X', 'cmdline_xoption', '-c', 'pass', 'arg2'], - 'parse_argv': 1, + 'parse_argv': 2, 'xoptions': [ 'config_xoption1=3', 'config_xoption2=', @@ -1042,7 +1045,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'orig_argv': ['python3', '-c', code, 'arg2'], 'program_name': './python3', 'run_command': code + '\n', - 'parse_argv': 1, + 'parse_argv': 2, } self.check_all_configs("test_init_run_main", config, api=API_PYTHON) @@ -1056,7 +1059,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'arg2'], 'program_name': './python3', 'run_command': code + '\n', - 'parse_argv': 1, + 'parse_argv': 2, '_init_main': 0, } self.check_all_configs("test_init_main", config, @@ -1065,7 +1068,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): def test_init_parse_argv(self): config = { - 'parse_argv': 1, + 'parse_argv': 2, 'argv': ['-c', 'arg1', '-v', 'arg3'], 'orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'], 'program_name': './argv0', @@ -1394,11 +1397,31 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): self.check_all_configs("test_init_warnoptions", config, preconfig, api=API_PYTHON) + def test_init_set_config(self): + config = { + '_init_main': 0, + 'bytes_warning': 2, + 'warnoptions': ['error::BytesWarning'], + } + self.check_all_configs("test_init_set_config", config, + api=API_ISOLATED) + def test_get_argc_argv(self): self.run_embedded_interpreter("test_get_argc_argv") # ignore output +class SetConfigTests(unittest.TestCase): + def test_set_config(self): + # bpo-42260: Test _PyInterpreterState_SetConfig() + cmd = [sys.executable, '-I', '-m', 'test._test_embed_set_config'] + proc = subprocess.run(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + self.assertEqual(proc.returncode, 0, + (proc.returncode, proc.stdout, proc.stderr)) + + class AuditingTests(EmbeddingTestsMixin, unittest.TestCase): def test_open_code_hook(self): self.run_embedded_interpreter("test_open_code_hook") diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 3431040f98..19b4fa5464 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -429,7 +429,7 @@ class TestEnum(unittest.TestCase): def test_reserved__sunder_(self): with self.assertRaisesRegex( ValueError, - '_sunder_ names, such as "_bad_", are reserved', + "_sunder_ names, such as '_bad_', are reserved", ): class Bad(Enum): _bad_ = 1 @@ -585,12 +585,15 @@ class TestEnum(unittest.TestCase): class Test1Enum(MyMethodEnum, int, MyStrEnum): One = 1 Two = 2 + self.assertTrue(Test1Enum._member_type_ is int) self.assertEqual(str(Test1Enum.One), 'MyStr') + self.assertEqual(format(Test1Enum.One, ''), 'MyStr') # class Test2Enum(MyStrEnum, MyMethodEnum): One = 1 Two = 2 self.assertEqual(str(Test2Enum.One), 'MyStr') + self.assertEqual(format(Test1Enum.One, ''), 'MyStr') def test_inherited_data_type(self): class HexInt(int): @@ -1146,6 +1149,7 @@ class TestEnum(unittest.TestCase): class auto_enum(type(Enum)): def __new__(metacls, cls, bases, classdict): temp = type(classdict)() + temp._cls_name = cls names = set(classdict._member_names) i = 0 for k in classdict._member_names: @@ -2021,6 +2025,32 @@ class TestEnum(unittest.TestCase): REVERT_ALL = "REVERT_ALL" RETRY = "RETRY" + def test_multiple_mixin_inherited(self): + class MyInt(int): + def __new__(cls, value): + return super().__new__(cls, value) + + class HexMixin: + def __repr__(self): + return hex(self) + + class MyIntEnum(HexMixin, MyInt, enum.Enum): + pass + + class Foo(MyIntEnum): + TEST = 1 + self.assertTrue(isinstance(Foo.TEST, MyInt)) + self.assertEqual(repr(Foo.TEST), "0x1") + + class Fee(MyIntEnum): + TEST = 1 + def __new__(cls, value): + value += 1 + member = int.__new__(cls, value) + member._value_ = value + return member + self.assertEqual(Fee.TEST, 2) + def test_empty_globals(self): # bpo-35717: sys._getframe(2).f_globals['__name__'] fails with KeyError # when using compile and exec because f_globals is empty @@ -2088,6 +2118,111 @@ class TestEnum(unittest.TestCase): class ThirdFailedStrEnum(StrEnum): one = '1' two = b'2', 'ascii', 9 + + def test_init_subclass_calling(self): + class MyEnum(Enum): + def __init_subclass__(cls, **kwds): + super(MyEnum, cls).__init_subclass__(**kwds) + self.assertFalse(cls.__dict__.get('_test', False)) + cls._test1 = 'MyEnum' + # + class TheirEnum(MyEnum): + def __init_subclass__(cls, **kwds): + super().__init_subclass__(**kwds) + cls._test2 = 'TheirEnum' + class WhoseEnum(TheirEnum): + def __init_subclass__(cls, **kwds): + pass + class NoEnum(WhoseEnum): + ONE = 1 + self.assertEqual(TheirEnum.__dict__['_test1'], 'MyEnum') + self.assertEqual(WhoseEnum.__dict__['_test1'], 'MyEnum') + self.assertEqual(WhoseEnum.__dict__['_test2'], 'TheirEnum') + self.assertFalse(NoEnum.__dict__.get('_test1', False)) + self.assertFalse(NoEnum.__dict__.get('_test2', False)) + # + class OurEnum(MyEnum): + def __init_subclass__(cls, **kwds): + cls._test2 = 'OurEnum' + class WhereEnum(OurEnum): + def __init_subclass__(cls, **kwds): + pass + class NeverEnum(WhereEnum): + ONE = 'one' + self.assertEqual(OurEnum.__dict__['_test1'], 'MyEnum') + self.assertFalse(WhereEnum.__dict__.get('_test1', False)) + self.assertEqual(WhereEnum.__dict__['_test2'], 'OurEnum') + self.assertFalse(NeverEnum.__dict__.get('_test1', False)) + self.assertFalse(NeverEnum.__dict__.get('_test2', False)) + + def test_init_subclass_parameter(self): + class multiEnum(Enum): + def __init_subclass__(cls, multi): + for member in cls: + member._as_parameter_ = multi * member.value + class E(multiEnum, multi=3): + A = 1 + B = 2 + self.assertEqual(E.A._as_parameter_, 3) + self.assertEqual(E.B._as_parameter_, 6) + + @unittest.skipUnless( + sys.version_info[:2] == (3, 9), + 'private variables are now normal attributes', + ) + def test_warning_for_private_variables(self): + with self.assertWarns(DeprecationWarning): + class Private(Enum): + __corporal = 'Radar' + self.assertEqual(Private._Private__corporal.value, 'Radar') + try: + with self.assertWarns(DeprecationWarning): + class Private(Enum): + __major_ = 'Hoolihan' + except ValueError: + pass + + def test_private_variable_is_normal_attribute(self): + class Private(Enum): + __corporal = 'Radar' + __major_ = 'Hoolihan' + self.assertEqual(Private._Private__corporal, 'Radar') + self.assertEqual(Private._Private__major_, 'Hoolihan') + + def test_strenum_auto(self): + class Strings(StrEnum): + ONE = auto() + TWO = auto() + self.assertEqual([Strings.ONE, Strings.TWO], ['one', 'two']) + + + def test_dynamic_members_with_static_methods(self): + # + foo_defines = {'FOO_CAT': 'aloof', 'BAR_DOG': 'friendly', 'FOO_HORSE': 'big'} + class Foo(Enum): + vars().update({ + k: v + for k, v in foo_defines.items() + if k.startswith('FOO_') + }) + def upper(self): + return self.value.upper() + self.assertEqual(list(Foo), [Foo.FOO_CAT, Foo.FOO_HORSE]) + self.assertEqual(Foo.FOO_CAT.value, 'aloof') + self.assertEqual(Foo.FOO_HORSE.upper(), 'BIG') + # + with self.assertRaisesRegex(TypeError, "'FOO_CAT' already defined as: 'aloof'"): + class FooBar(Enum): + vars().update({ + k: v + for k, v in foo_defines.items() + if k.startswith('FOO_') + }, + **{'FOO_CAT': 'small'}, + ) + def upper(self): + return self.value.upper() + class TestOrder(unittest.TestCase): @@ -2225,6 +2360,11 @@ class TestFlag(unittest.TestCase): self.assertEqual(repr(~(Open.RO | Open.CE)), '<Open.AC: 3>') self.assertEqual(repr(~(Open.WO | Open.CE)), '<Open.RW: 2>') + def test_format(self): + Perm = self.Perm + self.assertEqual(format(Perm.R, ''), 'Perm.R') + self.assertEqual(format(Perm.R | Perm.X, ''), 'Perm.R|X') + def test_or(self): Perm = self.Perm for i in Perm: @@ -2539,6 +2679,42 @@ class TestFlag(unittest.TestCase): 'at least one thread failed while creating composite members') self.assertEqual(256, len(seen), 'too many composite members created') + def test_init_subclass(self): + class MyEnum(Flag): + def __init_subclass__(cls, **kwds): + super().__init_subclass__(**kwds) + self.assertFalse(cls.__dict__.get('_test', False)) + cls._test1 = 'MyEnum' + # + class TheirEnum(MyEnum): + def __init_subclass__(cls, **kwds): + super(TheirEnum, cls).__init_subclass__(**kwds) + cls._test2 = 'TheirEnum' + class WhoseEnum(TheirEnum): + def __init_subclass__(cls, **kwds): + pass + class NoEnum(WhoseEnum): + ONE = 1 + self.assertEqual(TheirEnum.__dict__['_test1'], 'MyEnum') + self.assertEqual(WhoseEnum.__dict__['_test1'], 'MyEnum') + self.assertEqual(WhoseEnum.__dict__['_test2'], 'TheirEnum') + self.assertFalse(NoEnum.__dict__.get('_test1', False)) + self.assertFalse(NoEnum.__dict__.get('_test2', False)) + # + class OurEnum(MyEnum): + def __init_subclass__(cls, **kwds): + cls._test2 = 'OurEnum' + class WhereEnum(OurEnum): + def __init_subclass__(cls, **kwds): + pass + class NeverEnum(WhereEnum): + ONE = 1 + self.assertEqual(OurEnum.__dict__['_test1'], 'MyEnum') + self.assertFalse(WhereEnum.__dict__.get('_test1', False)) + self.assertEqual(WhereEnum.__dict__['_test2'], 'OurEnum') + self.assertFalse(NeverEnum.__dict__.get('_test1', False)) + self.assertFalse(NeverEnum.__dict__.get('_test2', False)) + class TestIntFlag(unittest.TestCase): """Tests of the IntFlags.""" @@ -2564,6 +2740,7 @@ class TestIntFlag(unittest.TestCase): def test_type(self): Perm = self.Perm + self.assertTrue(Perm._member_type_ is int) Open = self.Open for f in Perm: self.assertTrue(isinstance(f, Perm)) @@ -2643,6 +2820,11 @@ class TestIntFlag(unittest.TestCase): self.assertEqual(repr(~(Open.WO | Open.CE)), '<Open.RW: -524290>') self.assertEqual(repr(Open(~4)), '<Open.CE|AC|RW|WO: -5>') + def test_format(self): + Perm = self.Perm + self.assertEqual(format(Perm.R, ''), '4') + self.assertEqual(format(Perm.R | Perm.X, ''), '5') + def test_or(self): Perm = self.Perm for i in Perm: diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py index 5785cb4649..906bfc21a2 100644 --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -2,6 +2,7 @@ import unittest import operator import sys import pickle +import gc from test import support @@ -134,6 +135,18 @@ class EnumerateTestCase(unittest.TestCase, PickleTest): self.assertEqual(len(set(map(id, list(enumerate(self.seq))))), len(self.seq)) self.assertEqual(len(set(map(id, enumerate(self.seq)))), min(1,len(self.seq))) + @support.cpython_only + def test_enumerate_result_gc(self): + # bpo-42536: enumerate's tuple-reuse speed trick breaks the GC's + # assumptions about what can be untracked. Make sure we re-track result + # tuples whenever we reuse them. + it = self.enum([[]]) + gc.collect() + # That GC collection probably untracked the recycled internal result + # tuple, which is initialized to (None, None). Make sure it's re-tracked + # when it's mutated and returned from __next__: + self.assertTrue(gc.is_tracked(next(it))) + class MyEnum(enumerate): pass diff --git a/Lib/test/test_epoll.py b/Lib/test/test_epoll.py index 10f148fe5c..b623852f9e 100644 --- a/Lib/test/test_epoll.py +++ b/Lib/test/test_epoll.py @@ -160,44 +160,42 @@ class TestEPoll(unittest.TestCase): self.fail("epoll on closed fd didn't raise EBADF") def test_control_and_wait(self): + # create the epoll object client, server = self._connected_pair() - ep = select.epoll(16) ep.register(server.fileno(), select.EPOLLIN | select.EPOLLOUT | select.EPOLLET) ep.register(client.fileno(), select.EPOLLIN | select.EPOLLOUT | select.EPOLLET) + # EPOLLOUT now = time.monotonic() events = ep.poll(1, 4) then = time.monotonic() self.assertFalse(then - now > 0.1, then - now) - events.sort() expected = [(client.fileno(), select.EPOLLOUT), (server.fileno(), select.EPOLLOUT)] - expected.sort() - - self.assertEqual(events, expected) + self.assertEqual(sorted(events), sorted(expected)) - events = ep.poll(timeout=2.1, maxevents=4) + # no event + events = ep.poll(timeout=0.1, maxevents=4) self.assertFalse(events) - client.send(b"Hello!") - server.send(b"world!!!") + # send: EPOLLIN and EPOLLOUT + client.sendall(b"Hello!") + server.sendall(b"world!!!") now = time.monotonic() - events = ep.poll(1, 4) + events = ep.poll(1.0, 4) then = time.monotonic() self.assertFalse(then - now > 0.01) - events.sort() expected = [(client.fileno(), select.EPOLLIN | select.EPOLLOUT), (server.fileno(), select.EPOLLIN | select.EPOLLOUT)] - expected.sort() - - self.assertEqual(events, expected) + self.assertEqual(sorted(events), sorted(expected)) + # unregister, modify ep.unregister(client.fileno()) ep.modify(server.fileno(), select.EPOLLOUT) now = time.monotonic() diff --git a/Lib/test/test_exception_hierarchy.py b/Lib/test/test_exception_hierarchy.py index 43b4af8403..89fe9ddcef 100644 --- a/Lib/test/test_exception_hierarchy.py +++ b/Lib/test/test_exception_hierarchy.py @@ -40,10 +40,10 @@ class HierarchyTest(unittest.TestCase): self.assertIs(EnvironmentError, OSError) def test_socket_errors(self): - self.assertIs(socket.error, IOError) + self.assertIs(socket.error, OSError) self.assertIs(socket.gaierror.__base__, OSError) self.assertIs(socket.herror.__base__, OSError) - self.assertIs(socket.timeout.__base__, OSError) + self.assertIs(socket.timeout, TimeoutError) def test_select_error(self): self.assertIs(select.error, OSError) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 4dbf5fe5d5..864422390a 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -252,7 +252,7 @@ class ExceptionTests(unittest.TestCase): check('from __future__ import doesnt_exist', 1, 1) check('from __future__ import braces', 1, 1) check('x=1\nfrom __future__ import division', 2, 1) - check('foo(1=2)', 1, 5) + check('foo(1=2)', 1, 6) check('def f():\n x, y: int', 2, 3) check('[*x for x in xs]', 1, 2) check('foo(x for x in range(10), 100)', 1, 5) @@ -1046,7 +1046,7 @@ class ExceptionTests(unittest.TestCase): # tstate->recursion_depth is equal to (recursion_limit - 1) # and is equal to recursion_limit when _gen_throw() calls # PyErr_NormalizeException(). - recurse(setrecursionlimit(depth + 2) - depth - 1) + recurse(setrecursionlimit(depth + 2) - depth) finally: sys.setrecursionlimit(recursionlimit) print('Done.') @@ -1076,6 +1076,54 @@ class ExceptionTests(unittest.TestCase): b'while normalizing an exception', err) self.assertIn(b'Done.', out) + + def test_recursion_in_except_handler(self): + + def set_relative_recursion_limit(n): + depth = 1 + while True: + try: + sys.setrecursionlimit(depth) + except RecursionError: + depth += 1 + else: + break + sys.setrecursionlimit(depth+n) + + def recurse_in_except(): + try: + 1/0 + except: + recurse_in_except() + + def recurse_after_except(): + try: + 1/0 + except: + pass + recurse_after_except() + + def recurse_in_body_and_except(): + try: + recurse_in_body_and_except() + except: + recurse_in_body_and_except() + + recursionlimit = sys.getrecursionlimit() + try: + set_relative_recursion_limit(10) + for func in (recurse_in_except, recurse_after_except, recurse_in_body_and_except): + with self.subTest(func=func): + try: + func() + except RecursionError: + pass + else: + self.fail("Should have raised a RecursionError") + finally: + sys.setrecursionlimit(recursionlimit) + + @cpython_only def test_recursion_normalizing_with_no_memory(self): # Issue #30697. Test that in the abort that occurs when there is no @@ -1112,7 +1160,7 @@ class ExceptionTests(unittest.TestCase): except MemoryError as e: tb = e.__traceback__ else: - self.fail("Should have raises a MemoryError") + self.fail("Should have raised a MemoryError") return traceback.format_tb(tb) tb1 = raiseMemError() @@ -1440,5 +1488,88 @@ class ImportErrorTests(unittest.TestCase): self.assertEqual(exc.path, orig.path) +class PEP626Tests(unittest.TestCase): + + def lineno_after_raise(self, f, line): + try: + f() + except Exception as ex: + t = ex.__traceback__ + while t.tb_next: + t = t.tb_next + frame = t.tb_frame + self.assertEqual(frame.f_lineno-frame.f_code.co_firstlineno, line) + + def test_lineno_after_raise_simple(self): + def simple(): + 1/0 + pass + self.lineno_after_raise(simple, 1) + + def test_lineno_after_raise_in_except(self): + def in_except(): + try: + 1/0 + except: + 1/0 + pass + self.lineno_after_raise(in_except, 4) + + def test_lineno_after_other_except(self): + def other_except(): + try: + 1/0 + except TypeError as ex: + pass + self.lineno_after_raise(other_except, 3) + + def test_lineno_in_named_except(self): + def in_named_except(): + try: + 1/0 + except Exception as ex: + 1/0 + pass + self.lineno_after_raise(in_named_except, 4) + + def test_lineno_in_try(self): + def in_try(): + try: + 1/0 + finally: + pass + self.lineno_after_raise(in_try, 4) + + def test_lineno_in_finally_normal(self): + def in_finally_normal(): + try: + pass + finally: + 1/0 + pass + self.lineno_after_raise(in_finally_normal, 4) + + def test_lineno_in_finally_except(self): + def in_finally_except(): + try: + 1/0 + finally: + 1/0 + pass + self.lineno_after_raise(in_finally_except, 4) + + def test_lineno_after_with(self): + class Noop: + def __enter__(self): + return self + def __exit__(self, *args): + pass + def after_with(): + with Noop(): + 1/0 + pass + self.lineno_after_raise(after_with, 2) + + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_filecmp.py b/Lib/test/test_filecmp.py index ca9b4f354a..fa4f67b6eb 100644 --- a/Lib/test/test_filecmp.py +++ b/Lib/test/test_filecmp.py @@ -66,6 +66,8 @@ class DirCompareTestCase(unittest.TestCase): for dir in (self.dir, self.dir_same, self.dir_diff, self.dir_ignored): shutil.rmtree(dir, True) os.mkdir(dir) + subdir_path = os.path.join(dir, 'subdir') + os.mkdir(subdir_path) if self.caseinsensitive and dir is self.dir_same: fn = 'FiLe' # Verify case-insensitive comparison else: @@ -110,6 +112,11 @@ class DirCompareTestCase(unittest.TestCase): "Comparing mismatched directories fails") + def _assert_lists(self, actual, expected): + """Assert that two lists are equal, up to ordering.""" + self.assertEqual(sorted(actual), sorted(expected)) + + def test_dircmp(self): # Check attributes for comparison of two identical directories left_dir, right_dir = self.dir, self.dir_same @@ -117,10 +124,13 @@ class DirCompareTestCase(unittest.TestCase): self.assertEqual(d.left, left_dir) self.assertEqual(d.right, right_dir) if self.caseinsensitive: - self.assertEqual([d.left_list, d.right_list],[['file'], ['FiLe']]) + self._assert_lists(d.left_list, ['file', 'subdir']) + self._assert_lists(d.right_list, ['FiLe', 'subdir']) else: - self.assertEqual([d.left_list, d.right_list],[['file'], ['file']]) - self.assertEqual(d.common, ['file']) + self._assert_lists(d.left_list, ['file', 'subdir']) + self._assert_lists(d.right_list, ['file', 'subdir']) + self._assert_lists(d.common, ['file', 'subdir']) + self._assert_lists(d.common_dirs, ['subdir']) self.assertEqual(d.left_only, []) self.assertEqual(d.right_only, []) self.assertEqual(d.same_files, ['file']) @@ -128,6 +138,7 @@ class DirCompareTestCase(unittest.TestCase): expected_report = [ "diff {} {}".format(self.dir, self.dir_same), "Identical files : ['file']", + "Common subdirectories : ['subdir']", ] self._assert_report(d.report, expected_report) @@ -136,9 +147,10 @@ class DirCompareTestCase(unittest.TestCase): d = filecmp.dircmp(left_dir, right_dir) self.assertEqual(d.left, left_dir) self.assertEqual(d.right, right_dir) - self.assertEqual(d.left_list, ['file']) - self.assertEqual(d.right_list, ['file', 'file2']) - self.assertEqual(d.common, ['file']) + self._assert_lists(d.left_list, ['file', 'subdir']) + self._assert_lists(d.right_list, ['file', 'file2', 'subdir']) + self._assert_lists(d.common, ['file', 'subdir']) + self._assert_lists(d.common_dirs, ['subdir']) self.assertEqual(d.left_only, []) self.assertEqual(d.right_only, ['file2']) self.assertEqual(d.same_files, ['file']) @@ -147,6 +159,7 @@ class DirCompareTestCase(unittest.TestCase): "diff {} {}".format(self.dir, self.dir_diff), "Only in {} : ['file2']".format(self.dir_diff), "Identical files : ['file']", + "Common subdirectories : ['subdir']", ] self._assert_report(d.report, expected_report) @@ -159,9 +172,9 @@ class DirCompareTestCase(unittest.TestCase): d = filecmp.dircmp(left_dir, right_dir) self.assertEqual(d.left, left_dir) self.assertEqual(d.right, right_dir) - self.assertEqual(d.left_list, ['file', 'file2']) - self.assertEqual(d.right_list, ['file']) - self.assertEqual(d.common, ['file']) + self._assert_lists(d.left_list, ['file', 'file2', 'subdir']) + self._assert_lists(d.right_list, ['file', 'subdir']) + self._assert_lists(d.common, ['file', 'subdir']) self.assertEqual(d.left_only, ['file2']) self.assertEqual(d.right_only, []) self.assertEqual(d.same_files, ['file']) @@ -170,6 +183,7 @@ class DirCompareTestCase(unittest.TestCase): "diff {} {}".format(self.dir, self.dir_diff), "Only in {} : ['file2']".format(self.dir), "Identical files : ['file']", + "Common subdirectories : ['subdir']", ] self._assert_report(d.report, expected_report) @@ -183,24 +197,45 @@ class DirCompareTestCase(unittest.TestCase): "diff {} {}".format(self.dir, self.dir_diff), "Identical files : ['file']", "Differing files : ['file2']", + "Common subdirectories : ['subdir']", ] self._assert_report(d.report, expected_report) + def test_dircmp_subdirs_type(self): + """Check that dircmp.subdirs respects subclassing.""" + class MyDirCmp(filecmp.dircmp): + pass + d = MyDirCmp(self.dir, self.dir_diff) + sub_dirs = d.subdirs + self.assertEqual(list(sub_dirs.keys()), ['subdir']) + sub_dcmp = sub_dirs['subdir'] + self.assertEqual(type(sub_dcmp), MyDirCmp) + def test_report_partial_closure(self): left_dir, right_dir = self.dir, self.dir_same d = filecmp.dircmp(left_dir, right_dir) + left_subdir = os.path.join(left_dir, 'subdir') + right_subdir = os.path.join(right_dir, 'subdir') expected_report = [ "diff {} {}".format(self.dir, self.dir_same), "Identical files : ['file']", + "Common subdirectories : ['subdir']", + '', + "diff {} {}".format(left_subdir, right_subdir), ] self._assert_report(d.report_partial_closure, expected_report) def test_report_full_closure(self): left_dir, right_dir = self.dir, self.dir_same d = filecmp.dircmp(left_dir, right_dir) + left_subdir = os.path.join(left_dir, 'subdir') + right_subdir = os.path.join(right_dir, 'subdir') expected_report = [ "diff {} {}".format(self.dir, self.dir_same), "Identical files : ['file']", + "Common subdirectories : ['subdir']", + '', + "diff {} {}".format(left_subdir, right_subdir), ] self._assert_report(d.report_full_closure, expected_report) diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py index 9653e46ecc..6679bd3d88 100644 --- a/Lib/test/test_format.py +++ b/Lib/test/test_format.py @@ -275,9 +275,9 @@ class FormatTest(unittest.TestCase): test_exc_common('% %s', 1, ValueError, "unsupported format character '%' (0x25) at index 2") test_exc_common('%d', '1', TypeError, - "%d format: a number is required, not str") + "%d format: a real number is required, not str") test_exc_common('%d', b'1', TypeError, - "%d format: a number is required, not bytes") + "%d format: a real number is required, not bytes") test_exc_common('%x', '1', TypeError, "%x format: an integer is required, not str") test_exc_common('%x', 3.14, TypeError, diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py index 39658f22aa..2424911c7a 100644 --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -1036,7 +1036,7 @@ class TestTimeouts(TestCase): self.evt.set() try: conn, addr = self.sock.accept() - except socket.timeout: + except TimeoutError: pass else: conn.sendall(b"1 Hola mundo\n") diff --git a/Lib/test/test_gdb.py b/Lib/test/test_gdb.py index 44cb9a0f07..22c75bae98 100644 --- a/Lib/test/test_gdb.py +++ b/Lib/test/test_gdb.py @@ -51,11 +51,6 @@ if gdb_major_version < 7: "embedding. Saw %s.%s:\n%s" % (gdb_major_version, gdb_minor_version, gdb_version)) -if (gdb_major_version, gdb_minor_version) >= (9, 2): - # gdb 9.2 on Fedora Rawhide is not reliable, see: - # * https://bugs.python.org/issue41473 - # * https://bugzilla.redhat.com/show_bug.cgi?id=1866884 - raise unittest.SkipTest("https://bugzilla.redhat.com/show_bug.cgi?id=1866884") if not sysconfig.is_python_build(): raise unittest.SkipTest("test_gdb only works on source builds at the moment.") diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 2979cfb550..fd024dcec8 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -47,46 +47,45 @@ V = TypeVar('V') class BaseTest(unittest.TestCase): """Test basics.""" + generic_types = [type, tuple, list, dict, set, frozenset, enumerate, + defaultdict, deque, + SequenceMatcher, + dircmp, + FileInput, + OrderedDict, Counter, UserDict, UserList, + Pattern, Match, + partial, partialmethod, cached_property, + AbstractContextManager, AbstractAsyncContextManager, + Awaitable, Coroutine, + AsyncIterable, AsyncIterator, + AsyncGenerator, Generator, + Iterable, Iterator, + Reversible, + Container, Collection, + Mailbox, _PartialFile, + ContextVar, Token, + Field, + Set, MutableSet, + Mapping, MutableMapping, MappingView, + KeysView, ItemsView, ValuesView, + Sequence, MutableSequence, + MappingProxyType, AsyncGeneratorType, + DirEntry, + chain, + TemporaryDirectory, SpooledTemporaryFile, + Queue, SimpleQueue, + _AssertRaisesContext, + SplitResult, ParseResult, + ValueProxy, ApplyResult, + WeakSet, ReferenceType, ref, + ShareableList, MPSimpleQueue, + Future, _WorkItem, + Morsel] + if ctypes is not None: + generic_types.extend((ctypes.Array, ctypes.LibraryLoader)) def test_subscriptable(self): - types = [type, tuple, list, dict, set, frozenset, enumerate, - defaultdict, deque, - SequenceMatcher, - dircmp, - FileInput, - OrderedDict, Counter, UserDict, UserList, - Pattern, Match, - partial, partialmethod, cached_property, - AbstractContextManager, AbstractAsyncContextManager, - Awaitable, Coroutine, - AsyncIterable, AsyncIterator, - AsyncGenerator, Generator, - Iterable, Iterator, - Reversible, - Container, Collection, - Callable, - Mailbox, _PartialFile, - ContextVar, Token, - Field, - Set, MutableSet, - Mapping, MutableMapping, MappingView, - KeysView, ItemsView, ValuesView, - Sequence, MutableSequence, - MappingProxyType, AsyncGeneratorType, - DirEntry, - chain, - TemporaryDirectory, SpooledTemporaryFile, - Queue, SimpleQueue, - _AssertRaisesContext, - SplitResult, ParseResult, - ValueProxy, ApplyResult, - WeakSet, ReferenceType, ref, - ShareableList, MPSimpleQueue, - Future, _WorkItem, - Morsel] - if ctypes is not None: - types.extend((ctypes.Array, ctypes.LibraryLoader)) - for t in types: + for t in self.generic_types: if t is None: continue tname = t.__name__ @@ -293,5 +292,104 @@ class BaseTest(unittest.TestCase): for generic_alias_property in ("__origin__", "__args__", "__parameters__"): self.assertIn(generic_alias_property, dir_of_gen_alias) + def test_weakref(self): + for t in self.generic_types: + if t is None: + continue + tname = t.__name__ + with self.subTest(f"Testing {tname}"): + alias = t[int] + self.assertEqual(ref(alias)(), alias) + + def test_no_kwargs(self): + # bpo-42576 + with self.assertRaises(TypeError): + GenericAlias(bad=float) + + def test_subclassing_types_genericalias(self): + class SubClass(GenericAlias): ... + alias = SubClass(list, int) + class Bad(GenericAlias): + def __new__(cls, *args, **kwargs): + super().__new__(cls, *args, **kwargs) + + self.assertEqual(alias, list[int]) + with self.assertRaises(TypeError): + Bad(list, int, bad=int) + + def test_abc_callable(self): + # A separate test is needed for Callable since it uses a subclass of + # GenericAlias. + alias = Callable[[int, str], float] + with self.subTest("Testing subscription"): + self.assertIs(alias.__origin__, Callable) + self.assertEqual(alias.__args__, (int, str, float)) + self.assertEqual(alias.__parameters__, ()) + + with self.subTest("Testing instance checks"): + self.assertIsInstance(alias, GenericAlias) + + with self.subTest("Testing weakref"): + self.assertEqual(ref(alias)(), alias) + + with self.subTest("Testing pickling"): + s = pickle.dumps(alias) + loaded = pickle.loads(s) + self.assertEqual(alias.__origin__, loaded.__origin__) + self.assertEqual(alias.__args__, loaded.__args__) + self.assertEqual(alias.__parameters__, loaded.__parameters__) + + with self.subTest("Testing TypeVar substitution"): + C1 = Callable[[int, T], T] + C2 = Callable[[K, T], V] + C3 = Callable[..., T] + self.assertEqual(C1[str], Callable[[int, str], str]) + self.assertEqual(C2[int, float, str], Callable[[int, float], str]) + self.assertEqual(C3[int], Callable[..., int]) + + # multi chaining + C4 = C2[int, V, str] + self.assertEqual(repr(C4).split(".")[-1], "Callable[[int, ~V], str]") + self.assertEqual(repr(C4[dict]).split(".")[-1], "Callable[[int, dict], str]") + self.assertEqual(C4[dict], Callable[[int, dict], str]) + + with self.subTest("Testing type erasure"): + class C1(Callable): + def __call__(self): + return None + a = C1[[int], T] + self.assertIs(a().__class__, C1) + self.assertEqual(a().__orig_class__, C1[[int], T]) + + # bpo-42195 + with self.subTest("Testing collections.abc.Callable's consistency " + "with typing.Callable"): + c1 = typing.Callable[[int, str], dict] + c2 = Callable[[int, str], dict] + self.assertEqual(c1.__args__, c2.__args__) + self.assertEqual(hash(c1.__args__), hash(c2.__args__)) + + with self.subTest("Testing ParamSpec uses"): + P = typing.ParamSpec('P') + C1 = Callable[P, T] + # substitution + self.assertEqual(C1[int, str], Callable[[int], str]) + self.assertEqual(C1[[int, str], str], Callable[[int, str], str]) + self.assertEqual(repr(C1).split(".")[-1], "Callable[~P, ~T]") + self.assertEqual(repr(C1[int, str]).split(".")[-1], "Callable[[int], str]") + + C2 = Callable[P, int] + # special case in PEP 612 where + # X[int, str, float] == X[[int, str, float]] + self.assertEqual(C2[int, str, float], C2[[int, str, float]]) + self.assertEqual(repr(C2).split(".")[-1], "Callable[~P, int]") + self.assertEqual(repr(C2[int, str]).split(".")[-1], "Callable[[int, str], int]") + + with self.subTest("Testing Concatenate uses"): + P = typing.ParamSpec('P') + C1 = Callable[typing.Concatenate[int, P], int] + self.assertEqual(repr(C1), "collections.abc.Callable" + "[typing.Concatenate[int, ~P], int]") + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 4551011f5c..f50a455926 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -26,17 +26,26 @@ from http.client import HTTPException # Were we compiled --with-pydebug or with #define Py_DEBUG? COMPILED_WITH_PYDEBUG = hasattr(sys, 'gettotalrefcount') -c_hashlib = import_fresh_module('hashlib', fresh=['_hashlib']) -py_hashlib = import_fresh_module('hashlib', blocked=['_hashlib']) - +# default builtin hash module +default_builtin_hashes = {'md5', 'sha1', 'sha256', 'sha512', 'sha3', 'blake2'} +# --with-builtin-hashlib-hashes override builtin_hashes = sysconfig.get_config_var("PY_BUILTIN_HASHLIB_HASHES") if builtin_hashes is None: - builtin_hashes = {'md5', 'sha1', 'sha256', 'sha512', 'sha3', 'blake2'} + builtin_hashes = default_builtin_hashes else: builtin_hashes = { m.strip() for m in builtin_hashes.strip('"').lower().split(",") } +# hashlib with and without OpenSSL backend for PBKDF2 +# only import builtin_hashlib when all builtin hashes are available. +# Otherwise import prints noise on stderr +openssl_hashlib = import_fresh_module('hashlib', fresh=['_hashlib']) +if builtin_hashes == default_builtin_hashes: + builtin_hashlib = import_fresh_module('hashlib', blocked=['_hashlib']) +else: + builtin_hashlib = None + try: from _hashlib import HASH, HASHXOF, openssl_md_meth_names except ImportError: @@ -1032,16 +1041,16 @@ class KDFTests(unittest.TestCase): iterations=1, dklen=None) self.assertEqual(out, self.pbkdf2_results['sha1'][0][0]) + @unittest.skipIf(builtin_hashlib is None, "test requires builtin_hashlib") def test_pbkdf2_hmac_py(self): - self._test_pbkdf2_hmac(py_hashlib.pbkdf2_hmac, builtin_hashes) + self._test_pbkdf2_hmac(builtin_hashlib.pbkdf2_hmac, builtin_hashes) - @unittest.skipUnless(hasattr(c_hashlib, 'pbkdf2_hmac'), + @unittest.skipUnless(hasattr(openssl_hashlib, 'pbkdf2_hmac'), ' test requires OpenSSL > 1.0') def test_pbkdf2_hmac_c(self): - self._test_pbkdf2_hmac(c_hashlib.pbkdf2_hmac, openssl_md_meth_names) - + self._test_pbkdf2_hmac(openssl_hashlib.pbkdf2_hmac, openssl_md_meth_names) - @unittest.skipUnless(hasattr(c_hashlib, 'scrypt'), + @unittest.skipUnless(hasattr(hashlib, 'scrypt'), ' test requires OpenSSL > 1.1') def test_scrypt(self): for password, salt, n, r, p, expected in self.scrypt_test_vectors: diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py index 2859abb21f..c3d7c8feb1 100644 --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -3,7 +3,7 @@ Written by Cody A.W. Somerville <cody-somerville@ubuntu.com>, Josip Dzolonga, and Michael Otteneder for the 2007/08 GHOP contest. """ - +from collections import OrderedDict from http.server import BaseHTTPRequestHandler, HTTPServer, \ SimpleHTTPRequestHandler, CGIHTTPRequestHandler from http import server, HTTPStatus @@ -19,7 +19,7 @@ import shutil import email.message import email.utils import html -import http.client +import http, http.client import urllib.parse import tempfile import time @@ -588,6 +588,15 @@ print() print(os.environ["%s"]) """ +cgi_file6 = """\ +#!%s +import os + +print("Content-type: text/plain") +print() +print(repr(os.environ)) +""" + @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, "This test can't be run reliably as root (issue #13308).") @@ -666,6 +675,11 @@ class CGIHTTPServerTestCase(BaseTestCase): file5.write(cgi_file1 % self.pythonexe) os.chmod(self.file5_path, 0o777) + self.file6_path = os.path.join(self.cgi_dir, 'file6.py') + with open(self.file6_path, 'w', encoding='utf-8') as file6: + file6.write(cgi_file6 % self.pythonexe) + os.chmod(self.file6_path, 0o777) + os.chdir(self.parent_dir) def tearDown(self): @@ -685,6 +699,8 @@ class CGIHTTPServerTestCase(BaseTestCase): os.remove(self.file4_path) if self.file5_path: os.remove(self.file5_path) + if self.file6_path: + os.remove(self.file6_path) os.rmdir(self.cgi_child_dir) os.rmdir(self.cgi_dir) os.rmdir(self.cgi_dir_in_sub_dir) @@ -818,6 +834,23 @@ class CGIHTTPServerTestCase(BaseTestCase): finally: CGIHTTPRequestHandler.cgi_directories.remove('/sub/dir/cgi-bin') + def test_accept(self): + browser_accept = \ + 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' + tests = ( + ((('Accept', browser_accept),), browser_accept), + ((), ''), + # Hack case to get two values for the one header + ((('Accept', 'text/html'), ('ACCEPT', 'text/plain')), + 'text/html,text/plain'), + ) + for headers, expected in tests: + headers = OrderedDict(headers) + with self.subTest(headers): + res = self.request('/cgi-bin/file6.py', 'GET', headers=headers) + self.assertEqual(http.HTTPStatus.OK, res.status) + expected = f"'HTTP_ACCEPT': {expected!r}" + self.assertIn(expected.encode('ascii'), res.read()) class SocketlessRequestHandler(SimpleHTTPRequestHandler): diff --git a/Lib/test/test_idle.py b/Lib/test/test_idle.py index b205d35649..8756b76633 100644 --- a/Lib/test/test_idle.py +++ b/Lib/test/test_idle.py @@ -20,5 +20,5 @@ from idlelib.idle_test import load_tests if __name__ == '__main__': tk.NoDefaultRoot() unittest.main(exit=False) - tk._support_default_root = 1 + tk._support_default_root = True tk._default_root = None diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index 96bcb09261..0cab7897a9 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -476,7 +476,7 @@ class NewIMAPTestsMixin(): _, server = self._setup(TimeoutHandler) addr = server.server_address[1] - with self.assertRaises(socket.timeout): + with self.assertRaises(TimeoutError): client = self.imap_class("localhost", addr, timeout=0.001) def test_with_statement(self): diff --git a/Lib/test/test_importlib/builtin/test_loader.py b/Lib/test/test_importlib/builtin/test_loader.py index b1349ec5da..f6b6d97cd5 100644 --- a/Lib/test/test_importlib/builtin/test_loader.py +++ b/Lib/test/test_importlib/builtin/test_loader.py @@ -6,6 +6,7 @@ machinery = util.import_importlib('importlib.machinery') import sys import types import unittest +import warnings @unittest.skipIf(util.BUILTINS.good_name is None, 'no reasonable builtin module') class LoaderTests(abc.LoaderTests): @@ -24,7 +25,9 @@ class LoaderTests(abc.LoaderTests): self.assertIn(module.__name__, sys.modules) def load_module(self, name): - return self.machinery.BuiltinImporter.load_module(name) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + return self.machinery.BuiltinImporter.load_module(name) def test_module(self): # Common case. diff --git a/Lib/test/test_importlib/extension/test_loader.py b/Lib/test/test_importlib/extension/test_loader.py index abd612fcd9..22cf2dac5f 100644 --- a/Lib/test/test_importlib/extension/test_loader.py +++ b/Lib/test/test_importlib/extension/test_loader.py @@ -1,3 +1,4 @@ +from warnings import catch_warnings from .. import abc from .. import util @@ -7,6 +8,7 @@ import os.path import sys import types import unittest +import warnings import importlib.util import importlib from test.support.script_helper import assert_python_failure @@ -20,14 +22,18 @@ class LoaderTests(abc.LoaderTests): util.EXTENSIONS.file_path) def load_module(self, fullname): - return self.loader.load_module(fullname) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + return self.loader.load_module(fullname) def test_load_module_API(self): # Test the default argument for load_module(). - self.loader.load_module() - self.loader.load_module(None) - with self.assertRaises(ImportError): - self.load_module('XXX') + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + self.loader.load_module() + self.loader.load_module(None) + with self.assertRaises(ImportError): + self.load_module('XXX') def test_equality(self): other = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name, @@ -94,6 +100,21 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests): self.loader = self.machinery.ExtensionFileLoader( self.name, self.spec.origin) + def load_module(self): + '''Load the module from the test extension''' + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + return self.loader.load_module(self.name) + + def load_module_by_name(self, fullname): + '''Load a module from the test extension by name''' + origin = self.spec.origin + loader = self.machinery.ExtensionFileLoader(fullname, origin) + spec = importlib.util.spec_from_loader(fullname, loader) + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) + return module + # No extension module as __init__ available for testing. test_package = None @@ -157,19 +178,6 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests): with self.assertRaises(SystemError): module.call_state_registration_func(2) - def load_module(self): - '''Load the module from the test extension''' - return self.loader.load_module(self.name) - - def load_module_by_name(self, fullname): - '''Load a module from the test extension by name''' - origin = self.spec.origin - loader = self.machinery.ExtensionFileLoader(fullname, origin) - spec = importlib.util.spec_from_loader(fullname, loader) - module = importlib.util.module_from_spec(spec) - loader.exec_module(module) - return module - def test_load_submodule(self): '''Test loading a simulated submodule''' module = self.load_module_by_name('pkg.' + self.name) diff --git a/Lib/test/test_importlib/frozen/test_loader.py b/Lib/test/test_importlib/frozen/test_loader.py index 29ecff1774..8eaffa798a 100644 --- a/Lib/test/test_importlib/frozen/test_loader.py +++ b/Lib/test/test_importlib/frozen/test_loader.py @@ -161,19 +161,23 @@ class LoaderTests(abc.LoaderTests): "<module '__hello__' (frozen)>") def test_module_repr_indirect(self): - with util.uncache('__hello__'), captured_stdout(): - module = self.machinery.FrozenImporter.load_module('__hello__') - self.assertEqual(repr(module), - "<module '__hello__' (frozen)>") + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + with util.uncache('__hello__'), captured_stdout(): + module = self.machinery.FrozenImporter.load_module('__hello__') + self.assertEqual(repr(module), + "<module '__hello__' (frozen)>") # No way to trigger an error in a frozen module. test_state_after_failure = None def test_unloadable(self): - assert self.machinery.FrozenImporter.find_module('_not_real') is None - with self.assertRaises(ImportError) as cm: - self.machinery.FrozenImporter.load_module('_not_real') - self.assertEqual(cm.exception.name, '_not_real') + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + assert self.machinery.FrozenImporter.find_module('_not_real') is None + with self.assertRaises(ImportError) as cm: + self.machinery.FrozenImporter.load_module('_not_real') + self.assertEqual(cm.exception.name, '_not_real') (Frozen_LoaderTests, diff --git a/Lib/test/test_importlib/import_/test___loader__.py b/Lib/test/test_importlib/import_/test___loader__.py index 4b18093cf9..ecd83c6567 100644 --- a/Lib/test/test_importlib/import_/test___loader__.py +++ b/Lib/test/test_importlib/import_/test___loader__.py @@ -2,6 +2,7 @@ from importlib import machinery import sys import types import unittest +import warnings from .. import util @@ -45,25 +46,29 @@ class LoaderMock: class LoaderAttributeTests: def test___loader___missing(self): - module = types.ModuleType('blah') - try: - del module.__loader__ - except AttributeError: - pass - loader = LoaderMock() - loader.module = module - with util.uncache('blah'), util.import_state(meta_path=[loader]): - module = self.__import__('blah') - self.assertEqual(loader, module.__loader__) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + module = types.ModuleType('blah') + try: + del module.__loader__ + except AttributeError: + pass + loader = LoaderMock() + loader.module = module + with util.uncache('blah'), util.import_state(meta_path=[loader]): + module = self.__import__('blah') + self.assertEqual(loader, module.__loader__) def test___loader___is_None(self): - module = types.ModuleType('blah') - module.__loader__ = None - loader = LoaderMock() - loader.module = module - with util.uncache('blah'), util.import_state(meta_path=[loader]): - returned_module = self.__import__('blah') - self.assertEqual(loader, module.__loader__) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + module = types.ModuleType('blah') + module.__loader__ = None + loader = LoaderMock() + loader.module = module + with util.uncache('blah'), util.import_state(meta_path=[loader]): + returned_module = self.__import__('blah') + self.assertEqual(loader, module.__loader__) (Frozen_Tests, diff --git a/Lib/test/test_importlib/import_/test___package__.py b/Lib/test/test_importlib/import_/test___package__.py index 761b256b38..4a2b34e5f6 100644 --- a/Lib/test/test_importlib/import_/test___package__.py +++ b/Lib/test/test_importlib/import_/test___package__.py @@ -98,6 +98,16 @@ class FakeSpec: class Using__package__PEP302(Using__package__): mock_modules = util.mock_modules + def test_using___package__(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_using___package__() + + def test_spec_fallback(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_spec_fallback() + (Frozen_UsingPackagePEP302, Source_UsingPackagePEP302 @@ -155,6 +165,21 @@ class Setting__package__: class Setting__package__PEP302(Setting__package__, unittest.TestCase): mock_modules = util.mock_modules + def test_top_level(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_top_level() + + def test_package(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_package() + + def test_submodule(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_submodule() + class Setting__package__PEP451(Setting__package__, unittest.TestCase): mock_modules = util.mock_spec diff --git a/Lib/test/test_importlib/import_/test_api.py b/Lib/test/test_importlib/import_/test_api.py index 0cd9de4daf..35c26977ea 100644 --- a/Lib/test/test_importlib/import_/test_api.py +++ b/Lib/test/test_importlib/import_/test_api.py @@ -4,6 +4,7 @@ from importlib import machinery import sys import types import unittest +import warnings PKG_NAME = 'fine' SUBMOD_NAME = 'fine.bogus' @@ -100,6 +101,36 @@ class APITest: class OldAPITests(APITest): bad_finder_loader = BadLoaderFinder + def test_raises_ModuleNotFoundError(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_raises_ModuleNotFoundError() + + def test_name_requires_rparition(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_name_requires_rparition() + + def test_negative_level(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_negative_level() + + def test_nonexistent_fromlist_entry(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_nonexistent_fromlist_entry() + + def test_fromlist_load_error_propagates(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_fromlist_load_error_propagates + + def test_blocked_fromlist(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_blocked_fromlist() + (Frozen_OldAPITests, Source_OldAPITests diff --git a/Lib/test/test_importlib/import_/test_caching.py b/Lib/test/test_importlib/import_/test_caching.py index 8079add5b2..0f987b2210 100644 --- a/Lib/test/test_importlib/import_/test_caching.py +++ b/Lib/test/test_importlib/import_/test_caching.py @@ -3,6 +3,7 @@ from .. import util import sys from types import MethodType import unittest +import warnings class UseCache: @@ -63,30 +64,36 @@ class ImportlibUseCache(UseCache, unittest.TestCase): # to when to use the module in sys.modules and when not to. def test_using_cache_after_loader(self): # [from cache on return] - with self.create_mock('module') as mock: - with util.import_state(meta_path=[mock]): - module = self.__import__('module') - self.assertEqual(id(module), id(sys.modules['module'])) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + with self.create_mock('module') as mock: + with util.import_state(meta_path=[mock]): + module = self.__import__('module') + self.assertEqual(id(module), id(sys.modules['module'])) # See test_using_cache_after_loader() for reasoning. def test_using_cache_for_assigning_to_attribute(self): # [from cache to attribute] - with self.create_mock('pkg.__init__', 'pkg.module') as importer: - with util.import_state(meta_path=[importer]): - module = self.__import__('pkg.module') - self.assertTrue(hasattr(module, 'module')) - self.assertEqual(id(module.module), - id(sys.modules['pkg.module'])) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + with self.create_mock('pkg.__init__', 'pkg.module') as importer: + with util.import_state(meta_path=[importer]): + module = self.__import__('pkg.module') + self.assertTrue(hasattr(module, 'module')) + self.assertEqual(id(module.module), + id(sys.modules['pkg.module'])) # See test_using_cache_after_loader() for reasoning. def test_using_cache_for_fromlist(self): # [from cache for fromlist] - with self.create_mock('pkg.__init__', 'pkg.module') as importer: - with util.import_state(meta_path=[importer]): - module = self.__import__('pkg', fromlist=['module']) - self.assertTrue(hasattr(module, 'module')) - self.assertEqual(id(module.module), - id(sys.modules['pkg.module'])) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + with self.create_mock('pkg.__init__', 'pkg.module') as importer: + with util.import_state(meta_path=[importer]): + module = self.__import__('pkg', fromlist=['module']) + self.assertTrue(hasattr(module, 'module')) + self.assertEqual(id(module.module), + id(sys.modules['pkg.module'])) if __name__ == '__main__': diff --git a/Lib/test/test_importlib/import_/test_fromlist.py b/Lib/test/test_importlib/import_/test_fromlist.py index 018c172176..deb21710a6 100644 --- a/Lib/test/test_importlib/import_/test_fromlist.py +++ b/Lib/test/test_importlib/import_/test_fromlist.py @@ -24,7 +24,7 @@ class ReturnValue: def test_return_from_from_import(self): # [from return] - with util.mock_modules('pkg.__init__', 'pkg.module')as importer: + with util.mock_spec('pkg.__init__', 'pkg.module')as importer: with util.import_state(meta_path=[importer]): module = self.__import__('pkg.module', fromlist=['attr']) self.assertEqual(module.__name__, 'pkg.module') @@ -52,14 +52,14 @@ class HandlingFromlist: def test_object(self): # [object case] - with util.mock_modules('module') as importer: + with util.mock_spec('module') as importer: with util.import_state(meta_path=[importer]): module = self.__import__('module', fromlist=['attr']) self.assertEqual(module.__name__, 'module') def test_nonexistent_object(self): # [bad object] - with util.mock_modules('module') as importer: + with util.mock_spec('module') as importer: with util.import_state(meta_path=[importer]): module = self.__import__('module', fromlist=['non_existent']) self.assertEqual(module.__name__, 'module') @@ -67,7 +67,7 @@ class HandlingFromlist: def test_module_from_package(self): # [module] - with util.mock_modules('pkg.__init__', 'pkg.module') as importer: + with util.mock_spec('pkg.__init__', 'pkg.module') as importer: with util.import_state(meta_path=[importer]): module = self.__import__('pkg', fromlist=['module']) self.assertEqual(module.__name__, 'pkg') @@ -75,7 +75,7 @@ class HandlingFromlist: self.assertEqual(module.module.__name__, 'pkg.module') def test_nonexistent_from_package(self): - with util.mock_modules('pkg.__init__') as importer: + with util.mock_spec('pkg.__init__') as importer: with util.import_state(meta_path=[importer]): module = self.__import__('pkg', fromlist=['non_existent']) self.assertEqual(module.__name__, 'pkg') @@ -87,7 +87,7 @@ class HandlingFromlist: # ModuleNotFoundError propagate. def module_code(): import i_do_not_exist - with util.mock_modules('pkg.__init__', 'pkg.mod', + with util.mock_spec('pkg.__init__', 'pkg.mod', module_code={'pkg.mod': module_code}) as importer: with util.import_state(meta_path=[importer]): with self.assertRaises(ModuleNotFoundError) as exc: @@ -95,14 +95,14 @@ class HandlingFromlist: self.assertEqual('i_do_not_exist', exc.exception.name) def test_empty_string(self): - with util.mock_modules('pkg.__init__', 'pkg.mod') as importer: + with util.mock_spec('pkg.__init__', 'pkg.mod') as importer: with util.import_state(meta_path=[importer]): module = self.__import__('pkg.mod', fromlist=['']) self.assertEqual(module.__name__, 'pkg.mod') def basic_star_test(self, fromlist=['*']): # [using *] - with util.mock_modules('pkg.__init__', 'pkg.module') as mock: + with util.mock_spec('pkg.__init__', 'pkg.module') as mock: with util.import_state(meta_path=[mock]): mock['pkg'].__all__ = ['module'] module = self.__import__('pkg', fromlist=fromlist) @@ -119,7 +119,7 @@ class HandlingFromlist: def test_star_with_others(self): # [using * with others] - context = util.mock_modules('pkg.__init__', 'pkg.module1', 'pkg.module2') + context = util.mock_spec('pkg.__init__', 'pkg.module1', 'pkg.module2') with context as mock: with util.import_state(meta_path=[mock]): mock['pkg'].__all__ = ['module1'] @@ -131,7 +131,7 @@ class HandlingFromlist: self.assertEqual(module.module2.__name__, 'pkg.module2') def test_nonexistent_in_all(self): - with util.mock_modules('pkg.__init__') as importer: + with util.mock_spec('pkg.__init__') as importer: with util.import_state(meta_path=[importer]): importer['pkg'].__all__ = ['non_existent'] module = self.__import__('pkg', fromlist=['*']) @@ -139,7 +139,7 @@ class HandlingFromlist: self.assertFalse(hasattr(module, 'non_existent')) def test_star_in_all(self): - with util.mock_modules('pkg.__init__') as importer: + with util.mock_spec('pkg.__init__') as importer: with util.import_state(meta_path=[importer]): importer['pkg'].__all__ = ['*'] module = self.__import__('pkg', fromlist=['*']) @@ -147,7 +147,7 @@ class HandlingFromlist: self.assertFalse(hasattr(module, '*')) def test_invalid_type(self): - with util.mock_modules('pkg.__init__') as importer: + with util.mock_spec('pkg.__init__') as importer: with util.import_state(meta_path=[importer]), \ warnings.catch_warnings(): warnings.simplefilter('error', BytesWarning) @@ -157,7 +157,7 @@ class HandlingFromlist: self.__import__('pkg', fromlist=iter([b'attr'])) def test_invalid_type_in_all(self): - with util.mock_modules('pkg.__init__') as importer: + with util.mock_spec('pkg.__init__') as importer: with util.import_state(meta_path=[importer]), \ warnings.catch_warnings(): warnings.simplefilter('error', BytesWarning) diff --git a/Lib/test/test_importlib/import_/test_meta_path.py b/Lib/test/test_importlib/import_/test_meta_path.py index 5a41e8968a..5730119fe9 100644 --- a/Lib/test/test_importlib/import_/test_meta_path.py +++ b/Lib/test/test_importlib/import_/test_meta_path.py @@ -100,8 +100,20 @@ class CallSignature: self.assertEqual(args[0], mod_name) self.assertIs(args[1], path) +class CallSignoreSuppressImportWarning(CallSignature): -class CallSignaturePEP302(CallSignature): + def test_no_path(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_no_path() + + def test_with_path(self): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + super().test_no_path() + + +class CallSignaturePEP302(CallSignoreSuppressImportWarning): mock_modules = util.mock_modules finder_name = 'find_module' diff --git a/Lib/test/test_importlib/test_abc.py b/Lib/test/test_importlib/test_abc.py index 605738fae2..d8b9fc89f2 100644 --- a/Lib/test/test_importlib/test_abc.py +++ b/Lib/test/test_importlib/test_abc.py @@ -458,32 +458,36 @@ class LoaderLoadModuleTests: return SpecLoader() def test_fresh(self): - loader = self.loader() - name = 'blah' - with test_util.uncache(name): - loader.load_module(name) - module = loader.found - self.assertIs(sys.modules[name], module) - self.assertEqual(loader, module.__loader__) - self.assertEqual(loader, module.__spec__.loader) - self.assertEqual(name, module.__name__) - self.assertEqual(name, module.__spec__.name) - self.assertIsNotNone(module.__path__) - self.assertIsNotNone(module.__path__, - module.__spec__.submodule_search_locations) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + loader = self.loader() + name = 'blah' + with test_util.uncache(name): + loader.load_module(name) + module = loader.found + self.assertIs(sys.modules[name], module) + self.assertEqual(loader, module.__loader__) + self.assertEqual(loader, module.__spec__.loader) + self.assertEqual(name, module.__name__) + self.assertEqual(name, module.__spec__.name) + self.assertIsNotNone(module.__path__) + self.assertIsNotNone(module.__path__, + module.__spec__.submodule_search_locations) def test_reload(self): - name = 'blah' - loader = self.loader() - module = types.ModuleType(name) - module.__spec__ = self.util.spec_from_loader(name, loader) - module.__loader__ = loader - with test_util.uncache(name): - sys.modules[name] = module - loader.load_module(name) - found = loader.found - self.assertIs(found, sys.modules[name]) - self.assertIs(module, sys.modules[name]) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + name = 'blah' + loader = self.loader() + module = types.ModuleType(name) + module.__spec__ = self.util.spec_from_loader(name, loader) + module.__loader__ = loader + with test_util.uncache(name): + sys.modules[name] = module + loader.load_module(name) + found = loader.found + self.assertIs(found, sys.modules[name]) + self.assertIs(module, sys.modules[name]) (Frozen_LoaderLoadModuleTests, @@ -837,25 +841,29 @@ class SourceOnlyLoaderTests(SourceLoaderTestHarness): # Loading a module should set __name__, __loader__, __package__, # __path__ (for packages), __file__, and __cached__. # The module should also be put into sys.modules. - with test_util.uncache(self.name): - with warnings.catch_warnings(): - warnings.simplefilter('ignore', DeprecationWarning) - module = self.loader.load_module(self.name) - self.verify_module(module) - self.assertEqual(module.__path__, [os.path.dirname(self.path)]) - self.assertIn(self.name, sys.modules) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + with test_util.uncache(self.name): + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + module = self.loader.load_module(self.name) + self.verify_module(module) + self.assertEqual(module.__path__, [os.path.dirname(self.path)]) + self.assertIn(self.name, sys.modules) def test_package_settings(self): # __package__ needs to be set, while __path__ is set on if the module # is a package. # Testing the values for a package are covered by test_load_module. - self.setUp(is_package=False) - with test_util.uncache(self.name): - with warnings.catch_warnings(): - warnings.simplefilter('ignore', DeprecationWarning) - module = self.loader.load_module(self.name) - self.verify_module(module) - self.assertFalse(hasattr(module, '__path__')) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + self.setUp(is_package=False) + with test_util.uncache(self.name): + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + module = self.loader.load_module(self.name) + self.verify_module(module) + self.assertFalse(hasattr(module, '__path__')) def test_get_source_encoding(self): # Source is considered encoded in UTF-8 by default unless otherwise diff --git a/Lib/test/test_importlib/test_api.py b/Lib/test/test_importlib/test_api.py index fd60634e09..3f06a10ba9 100644 --- a/Lib/test/test_importlib/test_api.py +++ b/Lib/test/test_importlib/test_api.py @@ -20,7 +20,7 @@ class ImportModuleTests: def test_module_import(self): # Test importing a top-level module. - with test_util.mock_modules('top_level') as mock: + with test_util.mock_spec('top_level') as mock: with test_util.import_state(meta_path=[mock]): module = self.init.import_module('top_level') self.assertEqual(module.__name__, 'top_level') @@ -30,7 +30,7 @@ class ImportModuleTests: pkg_name = 'pkg' pkg_long_name = '{0}.__init__'.format(pkg_name) name = '{0}.mod'.format(pkg_name) - with test_util.mock_modules(pkg_long_name, name) as mock: + with test_util.mock_spec(pkg_long_name, name) as mock: with test_util.import_state(meta_path=[mock]): module = self.init.import_module(name) self.assertEqual(module.__name__, name) @@ -42,7 +42,7 @@ class ImportModuleTests: module_name = 'mod' absolute_name = '{0}.{1}'.format(pkg_name, module_name) relative_name = '.{0}'.format(module_name) - with test_util.mock_modules(pkg_long_name, absolute_name) as mock: + with test_util.mock_spec(pkg_long_name, absolute_name) as mock: with test_util.import_state(meta_path=[mock]): self.init.import_module(pkg_name) module = self.init.import_module(relative_name, pkg_name) @@ -50,7 +50,7 @@ class ImportModuleTests: def test_deep_relative_package_import(self): modules = ['a.__init__', 'a.b.__init__', 'a.c'] - with test_util.mock_modules(*modules) as mock: + with test_util.mock_spec(*modules) as mock: with test_util.import_state(meta_path=[mock]): self.init.import_module('a') self.init.import_module('a.b') @@ -63,7 +63,7 @@ class ImportModuleTests: pkg_name = 'pkg' pkg_long_name = '{0}.__init__'.format(pkg_name) name = '{0}.mod'.format(pkg_name) - with test_util.mock_modules(pkg_long_name, name) as mock: + with test_util.mock_spec(pkg_long_name, name) as mock: with test_util.import_state(meta_path=[mock]): self.init.import_module(pkg_name) module = self.init.import_module(name, pkg_name) @@ -88,7 +88,7 @@ class ImportModuleTests: b_load_count += 1 code = {'a': load_a, 'a.b': load_b} modules = ['a.__init__', 'a.b'] - with test_util.mock_modules(*modules, module_code=code) as mock: + with test_util.mock_spec(*modules, module_code=code) as mock: with test_util.import_state(meta_path=[mock]): self.init.import_module('a.b') self.assertEqual(b_load_count, 1) @@ -212,8 +212,8 @@ class ReloadTests: module = type(sys)('top_level') module.spam = 3 sys.modules['top_level'] = module - mock = test_util.mock_modules('top_level', - module_code={'top_level': code}) + mock = test_util.mock_spec('top_level', + module_code={'top_level': code}) with mock: with test_util.import_state(meta_path=[mock]): module = self.init.import_module('top_level') diff --git a/Lib/test/test_importlib/test_spec.py b/Lib/test/test_importlib/test_spec.py index eed90f29f9..b57eb6c0ff 100644 --- a/Lib/test/test_importlib/test_spec.py +++ b/Lib/test/test_importlib/test_spec.py @@ -303,32 +303,38 @@ class ModuleSpecMethodsTests: self.assertNotIn(self.spec.name, sys.modules) def test_load_legacy(self): - self.spec.loader = LegacyLoader() - with CleanImport(self.spec.name): - loaded = self.bootstrap._load(self.spec) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + self.spec.loader = LegacyLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._load(self.spec) - self.assertEqual(loaded.ham, -1) + self.assertEqual(loaded.ham, -1) def test_load_legacy_attributes(self): - self.spec.loader = LegacyLoader() - with CleanImport(self.spec.name): - loaded = self.bootstrap._load(self.spec) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + self.spec.loader = LegacyLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._load(self.spec) - self.assertIs(loaded.__loader__, self.spec.loader) - self.assertEqual(loaded.__package__, self.spec.parent) - self.assertIs(loaded.__spec__, self.spec) + self.assertIs(loaded.__loader__, self.spec.loader) + self.assertEqual(loaded.__package__, self.spec.parent) + self.assertIs(loaded.__spec__, self.spec) def test_load_legacy_attributes_immutable(self): module = object() - class ImmutableLoader(TestLoader): - def load_module(self, name): - sys.modules[name] = module - return module - self.spec.loader = ImmutableLoader() - with CleanImport(self.spec.name): - loaded = self.bootstrap._load(self.spec) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + class ImmutableLoader(TestLoader): + def load_module(self, name): + sys.modules[name] = module + return module + self.spec.loader = ImmutableLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._load(self.spec) - self.assertIs(sys.modules[self.spec.name], module) + self.assertIs(sys.modules[self.spec.name], module) # reload() @@ -382,11 +388,13 @@ class ModuleSpecMethodsTests: self.assertFalse(hasattr(loaded, '__cached__')) def test_reload_legacy(self): - self.spec.loader = LegacyLoader() - with CleanImport(self.spec.name): - loaded = self.bootstrap._load(self.spec) - reloaded = self.bootstrap._exec(self.spec, loaded) - installed = sys.modules[self.spec.name] + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + self.spec.loader = LegacyLoader() + with CleanImport(self.spec.name): + loaded = self.bootstrap._load(self.spec) + reloaded = self.bootstrap._exec(self.spec, loaded) + installed = sys.modules[self.spec.name] self.assertEqual(loaded.ham, -1) self.assertIs(reloaded, loaded) diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 71c4f27d27..706fcbe343 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -390,6 +390,7 @@ class TestRetrievingSourceCode(GetSourceBase): ('ParrotDroppings', mod.ParrotDroppings), ('StupidGit', mod.StupidGit), ('Tit', mod.MalodorousPervert), + ('WhichComments', mod.WhichComments), ]) tree = inspect.getclasstree([cls[1] for cls in classes]) self.assertEqual(tree, @@ -403,7 +404,8 @@ class TestRetrievingSourceCode(GetSourceBase): [(mod.FesteringGob, (mod.MalodorousPervert, mod.ParrotDroppings)) ] - ] + ], + (mod.WhichComments, (object,),) ] ]) tree = inspect.getclasstree([cls[1] for cls in classes], True) @@ -415,7 +417,8 @@ class TestRetrievingSourceCode(GetSourceBase): [(mod.FesteringGob, (mod.MalodorousPervert, mod.ParrotDroppings)) ] - ] + ], + (mod.WhichComments, (object,),) ] ]) @@ -646,6 +649,18 @@ class TestOneliners(GetSourceBase): # as argument to another function. self.assertSourceEqual(mod2.anonymous, 55, 55) +class TestBlockComments(GetSourceBase): + fodderModule = mod + + def test_toplevel_class(self): + self.assertSourceEqual(mod.WhichComments, 96, 114) + + def test_class_method(self): + self.assertSourceEqual(mod.WhichComments.f, 99, 104) + + def test_class_async_method(self): + self.assertSourceEqual(mod.WhichComments.asyncf, 109, 112) + class TestBuggyCases(GetSourceBase): fodderModule = mod2 @@ -697,6 +712,17 @@ class TestBuggyCases(GetSourceBase): self.assertRaises(IOError, inspect.findsource, co) self.assertRaises(IOError, inspect.getsource, co) + def test_findsource_with_out_of_bounds_lineno(self): + mod_len = len(inspect.getsource(mod)) + src = '\n' * 2* mod_len + "def f(): pass" + co = compile(src, mod.__file__, "exec") + g, l = {}, {} + eval(co, g, l) + func = l['f'] + self.assertEqual(func.__code__.co_firstlineno, 1+2*mod_len) + with self.assertRaisesRegex(IOError, "lineno is out of bounds"): + inspect.findsource(func) + def test_getsource_on_method(self): self.assertSourceEqual(mod2.ClassWithMethod.method, 118, 119) @@ -3224,6 +3250,26 @@ class TestSignatureObject(unittest.TestCase): p2 = inspect.signature(lambda y, x: None).parameters self.assertNotEqual(p1, p2) + def test_signature_annotations_with_local_namespaces(self): + class Foo: ... + def func(foo: Foo) -> int: pass + def func2(foo: Foo, bar: Bar) -> int: pass + + for signature_func in (inspect.signature, inspect.Signature.from_callable): + with self.subTest(signature_func = signature_func): + sig1 = signature_func(func) + self.assertEqual(sig1.return_annotation, 'int') + self.assertEqual(sig1.parameters['foo'].annotation, 'Foo') + + sig2 = signature_func(func, localns=locals()) + self.assertEqual(sig2.return_annotation, int) + self.assertEqual(sig2.parameters['foo'].annotation, Foo) + + sig3 = signature_func(func2, globalns={'Bar': int}, localns=locals()) + self.assertEqual(sig3.return_annotation, int) + self.assertEqual(sig3.parameters['foo'].annotation, Foo) + self.assertEqual(sig3.parameters['bar'].annotation, int) + class TestParameterObject(unittest.TestCase): def test_signature_parameter_kinds(self): @@ -4014,8 +4060,8 @@ def foo(): def test_main(): run_unittest( - TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases, - TestInterpreterStack, TestClassesAndFunctions, TestPredicates, + TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBlockComments, + TestBuggyCases, TestInterpreterStack, TestClassesAndFunctions, TestPredicates, TestGetcallargsFunctions, TestGetcallargsMethods, TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState, TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject, diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index eaa6197bec..a99b5e2bb7 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -12,6 +12,8 @@ from functools import reduce import sys import struct import threading +import gc + maxsize = support.MAX_Py_ssize_t minsize = -maxsize-1 @@ -1024,6 +1026,25 @@ class TestBasicOps(unittest.TestCase): self.assertEqual(next(it), (1, 2)) self.assertRaises(RuntimeError, next, it) + def test_pairwise(self): + self.assertEqual(list(pairwise('')), []) + self.assertEqual(list(pairwise('a')), []) + self.assertEqual(list(pairwise('ab')), + [('a', 'b')]), + self.assertEqual(list(pairwise('abcde')), + [('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'e')]) + self.assertEqual(list(pairwise(range(10_000))), + list(zip(range(10_000), range(1, 10_000)))) + + with self.assertRaises(TypeError): + pairwise() # too few arguments + with self.assertRaises(TypeError): + pairwise('abc', 10) # too many arguments + with self.assertRaises(TypeError): + pairwise(iterable='abc') # keyword arguments + with self.assertRaises(TypeError): + pairwise(None) # non-iterable argument + def test_product(self): for args, result in [ ([], [()]), # zero iterables @@ -1554,6 +1575,51 @@ class TestBasicOps(unittest.TestCase): self.assertRaises(StopIteration, next, f(lambda x:x, [])) self.assertRaises(StopIteration, next, f(lambda x:x, StopNow())) + @support.cpython_only + def test_combinations_result_gc(self): + # bpo-42536: combinations's tuple-reuse speed trick breaks the GC's + # assumptions about what can be untracked. Make sure we re-track result + # tuples whenever we reuse them. + it = combinations([None, []], 1) + next(it) + gc.collect() + # That GC collection probably untracked the recycled internal result + # tuple, which has the value (None,). Make sure it's re-tracked when + # it's mutated and returned from __next__: + self.assertTrue(gc.is_tracked(next(it))) + + @support.cpython_only + def test_combinations_with_replacement_result_gc(self): + # Ditto for combinations_with_replacement. + it = combinations_with_replacement([None, []], 1) + next(it) + gc.collect() + self.assertTrue(gc.is_tracked(next(it))) + + @support.cpython_only + def test_permutations_result_gc(self): + # Ditto for permutations. + it = permutations([None, []], 1) + next(it) + gc.collect() + self.assertTrue(gc.is_tracked(next(it))) + + @support.cpython_only + def test_product_result_gc(self): + # Ditto for product. + it = product([None, []]) + next(it) + gc.collect() + self.assertTrue(gc.is_tracked(next(it))) + + @support.cpython_only + def test_zip_longest_result_gc(self): + # Ditto for zip_longest. + it = zip_longest([[]]) + gc.collect() + self.assertTrue(gc.is_tracked(next(it))) + + class TestExamples(unittest.TestCase): def test_accumulate(self): @@ -1787,6 +1853,10 @@ class TestGC(unittest.TestCase): a = [] self.makecycle(islice([a]*2, None), a) + def test_pairwise(self): + a = [] + self.makecycle(pairwise([a]*5), a) + def test_permutations(self): a = [] self.makecycle(permutations([1,2,a,3], 3), a) @@ -1995,6 +2065,17 @@ class TestVariousIteratorArgs(unittest.TestCase): self.assertRaises(TypeError, islice, N(s), 10) self.assertRaises(ZeroDivisionError, list, islice(E(s), 10)) + def test_pairwise(self): + for s in ("123", "", range(1000), ('do', 1.2), range(2000,2200,5)): + for g in (G, I, Ig, S, L, R): + seq = list(g(s)) + expected = list(zip(seq, seq[1:])) + actual = list(pairwise(g(s))) + self.assertEqual(actual, expected) + self.assertRaises(TypeError, pairwise, X(s)) + self.assertRaises(TypeError, pairwise, N(s)) + self.assertRaises(ZeroDivisionError, list, pairwise(E(s))) + def test_starmap(self): for s in (range(10), range(0), range(100), (7,11), range(20,50,5)): for g in (G, I, Ig, S, L, R): @@ -2290,7 +2371,7 @@ Samuele ... "Count how many times the predicate is true" ... return sum(map(pred, iterable)) ->>> def padnone(iterable): +>>> def pad_none(iterable): ... "Returns the sequence elements and then returns None indefinitely" ... return chain(iterable, repeat(None)) @@ -2312,15 +2393,6 @@ Samuele ... else: ... return starmap(func, repeat(args, times)) ->>> def pairwise(iterable): -... "s -> (s0,s1), (s1,s2), (s2, s3), ..." -... a, b = tee(iterable) -... try: -... next(b) -... except StopIteration: -... pass -... return zip(a, b) - >>> def grouper(n, iterable, fillvalue=None): ... "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx" ... args = [iter(iterable)] * n @@ -2451,16 +2523,7 @@ True >>> take(5, map(int, repeatfunc(random.random))) [0, 0, 0, 0, 0] ->>> list(pairwise('abcd')) -[('a', 'b'), ('b', 'c'), ('c', 'd')] - ->>> list(pairwise([])) -[] - ->>> list(pairwise('a')) -[] - ->>> list(islice(padnone('abc'), 0, 6)) +>>> list(islice(pad_none('abc'), 0, 6)) ['a', 'b', 'c', None, None, None] >>> list(ncycles('abc', 3)) diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index e2196736dc..859baa4738 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -4219,6 +4219,15 @@ class ModuleLevelMiscTest(BaseTest): logging.disable(83) self.assertEqual(logging.root.manager.disable, 83) + self.assertRaises(ValueError, logging.disable, "doesnotexists") + + class _NotAnIntOrString: + pass + + self.assertRaises(TypeError, logging.disable, _NotAnIntOrString()) + + logging.disable("WARN") + # test the default value introduced in 3.7 # (Issue #28524) logging.disable() diff --git a/Lib/test/test_mailcap.py b/Lib/test/test_mailcap.py index 51a0c7da8b..ef9cad498a 100644 --- a/Lib/test/test_mailcap.py +++ b/Lib/test/test_mailcap.py @@ -4,6 +4,7 @@ import copy import test.support from test.support import os_helper import unittest +import sys # Location of mailcap file MAILCAPFILE = test.support.findfile("mailcap.txt") @@ -214,6 +215,7 @@ class FindmatchTest(unittest.TestCase): self._run_cases(cases) @unittest.skipUnless(os.name == "posix", "Requires 'test' command on system") + @unittest.skipIf(sys.platform == "vxworks", "'test' command is not supported on VxWorks") def test_test(self): # findmatch() will automatically check any "test" conditions and skip # the entry if the check fails. diff --git a/Lib/test/test_named_expressions.py b/Lib/test/test_named_expressions.py index c813830ce6..5908f12108 100644 --- a/Lib/test/test_named_expressions.py +++ b/Lib/test/test_named_expressions.py @@ -113,7 +113,7 @@ class NamedExpressionInvalidTest(unittest.TestCase): "assignment expression within a comprehension cannot be used in a class body"): exec(code, {}, {}) - def test_named_expression_invalid_rebinding_comprehension_iteration_variable(self): + def test_named_expression_invalid_rebinding_list_comprehension_iteration_variable(self): cases = [ ("Local reuse", 'i', "[i := 0 for i in range(5)]"), ("Nested reuse", 'j', "[[(j := 0) for i in range(5)] for j in range(5)]"), @@ -130,7 +130,7 @@ class NamedExpressionInvalidTest(unittest.TestCase): with self.assertRaisesRegex(SyntaxError, msg): exec(code, {}, {}) - def test_named_expression_invalid_rebinding_comprehension_inner_loop(self): + def test_named_expression_invalid_rebinding_list_comprehension_inner_loop(self): cases = [ ("Inner reuse", 'j', "[i for i in range(5) if (j := 0) for j in range(5)]"), ("Inner unpacking reuse", 'j', "[i for i in range(5) if (j := 0) for j, k in [(0, 1)]]"), @@ -145,7 +145,7 @@ class NamedExpressionInvalidTest(unittest.TestCase): with self.assertRaisesRegex(SyntaxError, msg): exec(f"lambda: {code}", {}) # Function scope - def test_named_expression_invalid_comprehension_iterable_expression(self): + def test_named_expression_invalid_list_comprehension_iterable_expression(self): cases = [ ("Top level", "[i for i in (i := range(5))]"), ("Inside tuple", "[i for i in (2, 3, i := range(5))]"), @@ -167,6 +167,60 @@ class NamedExpressionInvalidTest(unittest.TestCase): with self.assertRaisesRegex(SyntaxError, msg): exec(f"lambda: {code}", {}) # Function scope + def test_named_expression_invalid_rebinding_set_comprehension_iteration_variable(self): + cases = [ + ("Local reuse", 'i', "{i := 0 for i in range(5)}"), + ("Nested reuse", 'j', "{{(j := 0) for i in range(5)} for j in range(5)}"), + ("Reuse inner loop target", 'j', "{(j := 0) for i in range(5) for j in range(5)}"), + ("Unpacking reuse", 'i', "{i := 0 for i, j in {(0, 1)}}"), + ("Reuse in loop condition", 'i', "{i+1 for i in range(5) if (i := 0)}"), + ("Unreachable reuse", 'i', "{False or (i:=0) for i in range(5)}"), + ("Unreachable nested reuse", 'i', + "{(i, j) for i in range(5) for j in range(5) if True or (i:=10)}"), + ] + for case, target, code in cases: + msg = f"assignment expression cannot rebind comprehension iteration variable '{target}'" + with self.subTest(case=case): + with self.assertRaisesRegex(SyntaxError, msg): + exec(code, {}, {}) + + def test_named_expression_invalid_rebinding_set_comprehension_inner_loop(self): + cases = [ + ("Inner reuse", 'j', "{i for i in range(5) if (j := 0) for j in range(5)}"), + ("Inner unpacking reuse", 'j', "{i for i in range(5) if (j := 0) for j, k in {(0, 1)}}"), + ] + for case, target, code in cases: + msg = f"comprehension inner loop cannot rebind assignment expression target '{target}'" + with self.subTest(case=case): + with self.assertRaisesRegex(SyntaxError, msg): + exec(code, {}) # Module scope + with self.assertRaisesRegex(SyntaxError, msg): + exec(code, {}, {}) # Class scope + with self.assertRaisesRegex(SyntaxError, msg): + exec(f"lambda: {code}", {}) # Function scope + + def test_named_expression_invalid_set_comprehension_iterable_expression(self): + cases = [ + ("Top level", "{i for i in (i := range(5))}"), + ("Inside tuple", "{i for i in (2, 3, i := range(5))}"), + ("Inside list", "{i for i in {2, 3, i := range(5)}}"), + ("Different name", "{i for i in (j := range(5))}"), + ("Lambda expression", "{i for i in (lambda:(j := range(5)))()}"), + ("Inner loop", "{i for i in range(5) for j in (i := range(5))}"), + ("Nested comprehension", "{i for i in {j for j in (k := range(5))}}"), + ("Nested comprehension condition", "{i for i in {j for j in range(5) if (j := True)}}"), + ("Nested comprehension body", "{i for i in {(j := True) for j in range(5)}}"), + ] + msg = "assignment expression cannot be used in a comprehension iterable expression" + for case, code in cases: + with self.subTest(case=case): + with self.assertRaisesRegex(SyntaxError, msg): + exec(code, {}) # Module scope + with self.assertRaisesRegex(SyntaxError, msg): + exec(code, {}, {}) # Class scope + with self.assertRaisesRegex(SyntaxError, msg): + exec(f"lambda: {code}", {}) # Function scope + class NamedExpressionAssignmentTest(unittest.TestCase): @@ -271,6 +325,27 @@ class NamedExpressionAssignmentTest(unittest.TestCase): fib = {(c := a): (a := b) + (b := a + c) - b for __ in range(6)} self.assertEqual(fib, {1: 2, 2: 3, 3: 5, 5: 8, 8: 13, 13: 21}) + def test_named_expression_assignment_17(self): + a = [1] + element = a[b:=0] + self.assertEqual(b, 0) + self.assertEqual(element, a[0]) + + def test_named_expression_assignment_18(self): + class TwoDimensionalList: + def __init__(self, two_dimensional_list): + self.two_dimensional_list = two_dimensional_list + + def __getitem__(self, index): + return self.two_dimensional_list[index[0]][index[1]] + + a = TwoDimensionalList([[1], [2]]) + element = a[b:=0, c:=0] + self.assertEqual(b, 0) + self.assertEqual(c, 0) + self.assertEqual(element, a.two_dimensional_list[b][c]) + + class NamedExpressionScopeTest(unittest.TestCase): @@ -513,6 +588,15 @@ spam()""" self.assertEqual(nonlocal_var, None) f() + def test_named_expression_scope_in_genexp(self): + a = 1 + b = [1, 2, 3, 4] + genexp = (c := i + a for i in b) + + self.assertNotIn("c", locals()) + for idx, elem in enumerate(genexp): + self.assertEqual(elem, b[idx] + a) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_netrc.py b/Lib/test/test_netrc.py index 2bd46aa745..90ef5cd363 100644 --- a/Lib/test/test_netrc.py +++ b/Lib/test/test_netrc.py @@ -109,62 +109,56 @@ class NetrcTestCase(unittest.TestCase): def test_security(self): # This test is incomplete since we are normally not run as root and # therefore can't test the file ownership being wrong. - d = os_helper.TESTFN - os.mkdir(d) - self.addCleanup(os_helper.rmtree, d) - fn = os.path.join(d, '.netrc') - with open(fn, 'wt') as f: - f.write("""\ - machine foo.domain.com login bar password pass - default login foo password pass - """) - with os_helper.EnvironmentVarGuard() as environ: - environ.set('HOME', d) - os.chmod(fn, 0o600) - nrc = netrc.netrc() - self.assertEqual(nrc.hosts['foo.domain.com'], - ('bar', None, 'pass')) - os.chmod(fn, 0o622) - self.assertRaises(netrc.NetrcParseError, netrc.netrc) + with os_helper.temp_cwd(None) as d: + fn = os.path.join(d, '.netrc') + with open(fn, 'wt') as f: + f.write("""\ + machine foo.domain.com login bar password pass + default login foo password pass + """) + with os_helper.EnvironmentVarGuard() as environ: + environ.set('HOME', d) + os.chmod(fn, 0o600) + nrc = netrc.netrc() + self.assertEqual(nrc.hosts['foo.domain.com'], + ('bar', None, 'pass')) + os.chmod(fn, 0o622) + self.assertRaises(netrc.NetrcParseError, netrc.netrc) def test_file_not_found_in_home(self): - d = os_helper.TESTFN - os.mkdir(d) - self.addCleanup(os_helper.rmtree, d) - with os_helper.EnvironmentVarGuard() as environ: - environ.set('HOME', d) - self.assertRaises(FileNotFoundError, netrc.netrc) + with os_helper.temp_cwd(None) as d: + with os_helper.EnvironmentVarGuard() as environ: + environ.set('HOME', d) + self.assertRaises(FileNotFoundError, netrc.netrc) def test_file_not_found_explicit(self): self.assertRaises(FileNotFoundError, netrc.netrc, file='unlikely_netrc') def test_home_not_set(self): - fake_home = os_helper.TESTFN - os.mkdir(fake_home) - self.addCleanup(os_helper.rmtree, fake_home) - fake_netrc_path = os.path.join(fake_home, '.netrc') - with open(fake_netrc_path, 'w') as f: - f.write('machine foo.domain.com login bar password pass') - os.chmod(fake_netrc_path, 0o600) - - orig_expanduser = os.path.expanduser - called = [] - - def fake_expanduser(s): - called.append(s) - with os_helper.EnvironmentVarGuard() as environ: - environ.set('HOME', fake_home) - environ.set('USERPROFILE', fake_home) - result = orig_expanduser(s) - return result - - with support.swap_attr(os.path, 'expanduser', fake_expanduser): - nrc = netrc.netrc() - login, account, password = nrc.authenticators('foo.domain.com') - self.assertEqual(login, 'bar') - - self.assertTrue(called) + with os_helper.temp_cwd(None) as fake_home: + fake_netrc_path = os.path.join(fake_home, '.netrc') + with open(fake_netrc_path, 'w') as f: + f.write('machine foo.domain.com login bar password pass') + os.chmod(fake_netrc_path, 0o600) + + orig_expanduser = os.path.expanduser + called = [] + + def fake_expanduser(s): + called.append(s) + with os_helper.EnvironmentVarGuard() as environ: + environ.set('HOME', fake_home) + environ.set('USERPROFILE', fake_home) + result = orig_expanduser(s) + return result + + with support.swap_attr(os.path, 'expanduser', fake_expanduser): + nrc = netrc.netrc() + login, account, password = nrc.authenticators('foo.domain.com') + self.assertEqual(login, 'bar') + + self.assertTrue(called) if __name__ == "__main__": diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py new file mode 100644 index 0000000000..61f337d70e --- /dev/null +++ b/Lib/test/test_opcache.py @@ -0,0 +1,23 @@ +import unittest + +class TestLoadAttrCache(unittest.TestCase): + def test_descriptor_added_after_optimization(self): + class Descriptor: + pass + + class C: + def __init__(self): + self.x = 1 + x = Descriptor() + + def f(o): + return o.x + + o = C() + for i in range(1025): + assert f(o) == 1 + + Descriptor.__get__ = lambda self, instance, value: 2 + Descriptor.__set__ = lambda *args: None + + self.assertEqual(f(o), 2) diff --git a/Lib/test/test_opcodes.py b/Lib/test/test_opcodes.py index 1152eb65bb..e510364721 100644 --- a/Lib/test/test_opcodes.py +++ b/Lib/test/test_opcodes.py @@ -27,7 +27,7 @@ class OpcodeTest(unittest.TestCase): with open(ann_module.__file__) as f: txt = f.read() co = compile(txt, ann_module.__file__, 'exec') - self.assertEqual(co.co_firstlineno, 3) + self.assertEqual(co.co_firstlineno, 1) except OSError: pass diff --git a/Lib/test/test_ordered_dict.py b/Lib/test/test_ordered_dict.py index 31759f20d2..eb404463e9 100644 --- a/Lib/test/test_ordered_dict.py +++ b/Lib/test/test_ordered_dict.py @@ -700,6 +700,17 @@ class OrderedDictTests: with self.assertRaises(ValueError): a |= "BAD" + @support.cpython_only + def test_ordered_dict_items_result_gc(self): + # bpo-42536: OrderedDict.items's tuple-reuse speed trick breaks the GC's + # assumptions about what can be untracked. Make sure we re-track result + # tuples whenever we reuse them. + it = iter(self.OrderedDict({None: []}).items()) + gc.collect() + # That GC collection probably untracked the recycled internal result + # tuple, which is initialized to (None, None). Make sure it's re-tracked + # when it's mutated and returned from __next__: + self.assertTrue(gc.is_tracked(next(it))) class PurePythonOrderedDictTests(OrderedDictTests, unittest.TestCase): diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 5126c84cf3..08d7ab8a30 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -15,10 +15,12 @@ import locale import mmap import os import pickle +import select import shutil import signal import socket import stat +import struct import subprocess import sys import sysconfig @@ -59,6 +61,7 @@ try: except ImportError: INT_MAX = PY_SSIZE_T_MAX = sys.maxsize + from test.support.script_helper import assert_python_ok from test.support import unix_shell from test.support.os_helper import FakePath @@ -90,6 +93,11 @@ def create_file(filename, content=b'content'): fp.write(content) +# bpo-41625: On AIX, splice() only works with a socket, not with a pipe. +requires_splice_pipe = unittest.skipIf(sys.platform.startswith("aix"), + 'on AIX, splice() only accepts sockets') + + class MiscTests(unittest.TestCase): def test_getcwd(self): cwd = os.getcwd() @@ -108,6 +116,10 @@ class MiscTests(unittest.TestCase): # than MAX_PATH if long paths support is disabled: # see RtlAreLongPathsEnabled(). min_len = 2000 # characters + # On VxWorks, PATH_MAX is defined as 1024 bytes. Creating a path + # longer than PATH_MAX will fail. + if sys.platform == 'vxworks': + min_len = 1000 dirlen = 200 # characters dirname = 'python_test_dir_' dirname = dirname + ('a' * (dirlen - len(dirname))) @@ -378,6 +390,126 @@ class FileTests(unittest.TestCase): self.assertEqual(read[out_seek:], data[in_skip:in_skip+i]) + @unittest.skipUnless(hasattr(os, 'splice'), 'test needs os.splice()') + def test_splice_invalid_values(self): + with self.assertRaises(ValueError): + os.splice(0, 1, -10) + + @unittest.skipUnless(hasattr(os, 'splice'), 'test needs os.splice()') + @requires_splice_pipe + def test_splice(self): + TESTFN2 = os_helper.TESTFN + ".3" + data = b'0123456789' + + create_file(os_helper.TESTFN, data) + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + + in_file = open(os_helper.TESTFN, 'rb') + self.addCleanup(in_file.close) + in_fd = in_file.fileno() + + read_fd, write_fd = os.pipe() + self.addCleanup(lambda: os.close(read_fd)) + self.addCleanup(lambda: os.close(write_fd)) + + try: + i = os.splice(in_fd, write_fd, 5) + except OSError as e: + # Handle the case in which Python was compiled + # in a system with the syscall but without support + # in the kernel. + if e.errno != errno.ENOSYS: + raise + self.skipTest(e) + else: + # The number of copied bytes can be less than + # the number of bytes originally requested. + self.assertIn(i, range(0, 6)); + + self.assertEqual(os.read(read_fd, 100), data[:i]) + + @unittest.skipUnless(hasattr(os, 'splice'), 'test needs os.splice()') + @requires_splice_pipe + def test_splice_offset_in(self): + TESTFN4 = os_helper.TESTFN + ".4" + data = b'0123456789' + bytes_to_copy = 6 + in_skip = 3 + + create_file(os_helper.TESTFN, data) + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + + in_file = open(os_helper.TESTFN, 'rb') + self.addCleanup(in_file.close) + in_fd = in_file.fileno() + + read_fd, write_fd = os.pipe() + self.addCleanup(lambda: os.close(read_fd)) + self.addCleanup(lambda: os.close(write_fd)) + + try: + i = os.splice(in_fd, write_fd, bytes_to_copy, offset_src=in_skip) + except OSError as e: + # Handle the case in which Python was compiled + # in a system with the syscall but without support + # in the kernel. + if e.errno != errno.ENOSYS: + raise + self.skipTest(e) + else: + # The number of copied bytes can be less than + # the number of bytes originally requested. + self.assertIn(i, range(0, bytes_to_copy+1)); + + read = os.read(read_fd, 100) + # 012 are skipped (in_skip) + # 345678 are copied in the file (in_skip + bytes_to_copy) + self.assertEqual(read, data[in_skip:in_skip+i]) + + @unittest.skipUnless(hasattr(os, 'splice'), 'test needs os.splice()') + @requires_splice_pipe + def test_splice_offset_out(self): + TESTFN4 = os_helper.TESTFN + ".4" + data = b'0123456789' + bytes_to_copy = 6 + out_seek = 3 + + create_file(os_helper.TESTFN, data) + self.addCleanup(os_helper.unlink, os_helper.TESTFN) + + read_fd, write_fd = os.pipe() + self.addCleanup(lambda: os.close(read_fd)) + self.addCleanup(lambda: os.close(write_fd)) + os.write(write_fd, data) + + out_file = open(TESTFN4, 'w+b') + self.addCleanup(os_helper.unlink, TESTFN4) + self.addCleanup(out_file.close) + out_fd = out_file.fileno() + + try: + i = os.splice(read_fd, out_fd, bytes_to_copy, offset_dst=out_seek) + except OSError as e: + # Handle the case in which Python was compiled + # in a system with the syscall but without support + # in the kernel. + if e.errno != errno.ENOSYS: + raise + self.skipTest(e) + else: + # The number of copied bytes can be less than + # the number of bytes originally requested. + self.assertIn(i, range(0, bytes_to_copy+1)); + + with open(TESTFN4, 'rb') as in_file: + read = in_file.read() + # seeked bytes (5) are zero'ed + self.assertEqual(read[:out_seek], b'\x00'*out_seek) + # 012 are skipped (in_skip) + # 345678 are copied in the file (in_skip + bytes_to_copy) + self.assertEqual(read[out_seek:], data[:i]) + + # Test attributes on return values from os.*stat* family. class StatAttributeTests(unittest.TestCase): def setUp(self): @@ -859,6 +991,7 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol): # Bug 1110478 @unittest.skipUnless(unix_shell and os.path.exists(unix_shell), 'requires a shell') + @unittest.skipUnless(hasattr(os, 'popen'), "needs os.popen()") def test_update2(self): os.environ.clear() os.environ.update(HELLO="World") @@ -868,6 +1001,7 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol): @unittest.skipUnless(unix_shell and os.path.exists(unix_shell), 'requires a shell') + @unittest.skipUnless(hasattr(os, 'popen'), "needs os.popen()") def test_os_popen_iter(self): with os.popen("%s -c 'echo \"line1\nline2\nline3\"'" % unix_shell) as popen: @@ -3528,6 +3662,89 @@ class MemfdCreateTests(unittest.TestCase): self.assertFalse(os.get_inheritable(fd2)) +@unittest.skipUnless(hasattr(os, 'eventfd'), 'requires os.eventfd') +@support.requires_linux_version(2, 6, 30) +class EventfdTests(unittest.TestCase): + def test_eventfd_initval(self): + def pack(value): + """Pack as native uint64_t + """ + return struct.pack("@Q", value) + size = 8 # read/write 8 bytes + initval = 42 + fd = os.eventfd(initval) + self.assertNotEqual(fd, -1) + self.addCleanup(os.close, fd) + self.assertFalse(os.get_inheritable(fd)) + + # test with raw read/write + res = os.read(fd, size) + self.assertEqual(res, pack(initval)) + + os.write(fd, pack(23)) + res = os.read(fd, size) + self.assertEqual(res, pack(23)) + + os.write(fd, pack(40)) + os.write(fd, pack(2)) + res = os.read(fd, size) + self.assertEqual(res, pack(42)) + + # test with eventfd_read/eventfd_write + os.eventfd_write(fd, 20) + os.eventfd_write(fd, 3) + res = os.eventfd_read(fd) + self.assertEqual(res, 23) + + def test_eventfd_semaphore(self): + initval = 2 + flags = os.EFD_CLOEXEC | os.EFD_SEMAPHORE | os.EFD_NONBLOCK + fd = os.eventfd(initval, flags) + self.assertNotEqual(fd, -1) + self.addCleanup(os.close, fd) + + # semaphore starts has initval 2, two reads return '1' + res = os.eventfd_read(fd) + self.assertEqual(res, 1) + res = os.eventfd_read(fd) + self.assertEqual(res, 1) + # third read would block + with self.assertRaises(BlockingIOError): + os.eventfd_read(fd) + with self.assertRaises(BlockingIOError): + os.read(fd, 8) + + # increase semaphore counter, read one + os.eventfd_write(fd, 1) + res = os.eventfd_read(fd) + self.assertEqual(res, 1) + # next read would block, too + with self.assertRaises(BlockingIOError): + os.eventfd_read(fd) + + def test_eventfd_select(self): + flags = os.EFD_CLOEXEC | os.EFD_NONBLOCK + fd = os.eventfd(0, flags) + self.assertNotEqual(fd, -1) + self.addCleanup(os.close, fd) + + # counter is zero, only writeable + rfd, wfd, xfd = select.select([fd], [fd], [fd], 0) + self.assertEqual((rfd, wfd, xfd), ([], [fd], [])) + + # counter is non-zero, read and writeable + os.eventfd_write(fd, 23) + rfd, wfd, xfd = select.select([fd], [fd], [fd], 0) + self.assertEqual((rfd, wfd, xfd), ([fd], [fd], [])) + self.assertEqual(os.eventfd_read(fd), 23) + + # counter at max, only readable + os.eventfd_write(fd, (2**64) - 2) + rfd, wfd, xfd = select.select([fd], [fd], [fd], 0) + self.assertEqual((rfd, wfd, xfd), ([fd], [], [])) + os.eventfd_read(fd) + + class OSErrorTests(unittest.TestCase): def setUp(self): class Str(str): diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 17292dc1ab..9be72941d3 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -440,9 +440,18 @@ class _BasePurePathTest(object): self.assertEqual(par[0], P('a/b')) self.assertEqual(par[1], P('a')) self.assertEqual(par[2], P('.')) + self.assertEqual(par[-1], P('.')) + self.assertEqual(par[-2], P('a')) + self.assertEqual(par[-3], P('a/b')) + self.assertEqual(par[0:1], (P('a/b'),)) + self.assertEqual(par[:2], (P('a/b'), P('a'))) + self.assertEqual(par[:-1], (P('a/b'), P('a'))) + self.assertEqual(par[1:], (P('a'), P('.'))) + self.assertEqual(par[::2], (P('a/b'), P('.'))) + self.assertEqual(par[::-1], (P('.'), P('a'), P('a/b'))) self.assertEqual(list(par), [P('a/b'), P('a'), P('.')]) with self.assertRaises(IndexError): - par[-1] + par[-4] with self.assertRaises(IndexError): par[3] with self.assertRaises(TypeError): @@ -454,6 +463,12 @@ class _BasePurePathTest(object): self.assertEqual(par[0], P('/a/b')) self.assertEqual(par[1], P('/a')) self.assertEqual(par[2], P('/')) + self.assertEqual(par[0:1], (P('/a/b'),)) + self.assertEqual(par[:2], (P('/a/b'), P('/a'))) + self.assertEqual(par[:-1], (P('/a/b'), P('/a'))) + self.assertEqual(par[1:], (P('/a'), P('/'))) + self.assertEqual(par[::2], (P('/a/b'), P('/'))) + self.assertEqual(par[::-1], (P('/'), P('/a'), P('/a/b'))) self.assertEqual(list(par), [P('/a/b'), P('/a'), P('/')]) with self.assertRaises(IndexError): par[3] @@ -905,6 +920,12 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase): self.assertEqual(len(par), 2) self.assertEqual(par[0], P('z:a')) self.assertEqual(par[1], P('z:')) + self.assertEqual(par[0:1], (P('z:a'),)) + self.assertEqual(par[:-1], (P('z:a'),)) + self.assertEqual(par[:2], (P('z:a'), P('z:'))) + self.assertEqual(par[1:], (P('z:'),)) + self.assertEqual(par[::2], (P('z:a'),)) + self.assertEqual(par[::-1], (P('z:'), P('z:a'))) self.assertEqual(list(par), [P('z:a'), P('z:')]) with self.assertRaises(IndexError): par[2] @@ -913,6 +934,12 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase): self.assertEqual(len(par), 2) self.assertEqual(par[0], P('z:/a')) self.assertEqual(par[1], P('z:/')) + self.assertEqual(par[0:1], (P('z:/a'),)) + self.assertEqual(par[0:-1], (P('z:/a'),)) + self.assertEqual(par[:2], (P('z:/a'), P('z:/'))) + self.assertEqual(par[1:], (P('z:/'),)) + self.assertEqual(par[::2], (P('z:/a'),)) + self.assertEqual(par[::-1], (P('z:/'), P('z:/a'),)) self.assertEqual(list(par), [P('z:/a'), P('z:/')]) with self.assertRaises(IndexError): par[2] @@ -921,6 +948,12 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase): self.assertEqual(len(par), 2) self.assertEqual(par[0], P('//a/b/c')) self.assertEqual(par[1], P('//a/b')) + self.assertEqual(par[0:1], (P('//a/b/c'),)) + self.assertEqual(par[0:-1], (P('//a/b/c'),)) + self.assertEqual(par[:2], (P('//a/b/c'), P('//a/b'))) + self.assertEqual(par[1:], (P('//a/b'),)) + self.assertEqual(par[::2], (P('//a/b/c'),)) + self.assertEqual(par[::-1], (P('//a/b'), P('//a/b/c'))) self.assertEqual(list(par), [P('//a/b/c'), P('//a/b')]) with self.assertRaises(IndexError): par[2] @@ -2186,6 +2219,8 @@ class _BasePathTest(object): self.assertIs((P / 'fileA\x00').is_fifo(), False) @unittest.skipUnless(hasattr(os, "mkfifo"), "os.mkfifo() required") + @unittest.skipIf(sys.platform == "vxworks", + "fifo requires special path on VxWorks") def test_is_fifo_true(self): P = self.cls(BASE, 'myfifo') try: @@ -2432,6 +2467,8 @@ class PosixPathTest(_BasePathTest, unittest.TestCase): @unittest.skipUnless(hasattr(pwd, 'getpwall'), 'pwd module does not expose getpwall()') + @unittest.skipIf(sys.platform == "vxworks", + "no home directory on VxWorks") def test_expanduser(self): P = self.cls import_helper.import_module('pwd') diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index e56451360d..4bb574fc5b 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -1645,9 +1645,10 @@ def bÅ“r(): 'debug doesnotexist', 'c', ]) - stdout, _ = self.run_pdb_script('', commands + '\n') + stdout, _ = self.run_pdb_script('pass', commands + '\n') self.assertEqual(stdout.splitlines()[1:], [ + '-> pass', '(Pdb) *** SyntaxError: unexpected EOF while parsing', '(Pdb) ENTERING RECURSIVE DEBUGGER', diff --git a/Lib/test/test_peepholer.py b/Lib/test/test_peepholer.py index 92a82cc54f..4034154e4d 100644 --- a/Lib/test/test_peepholer.py +++ b/Lib/test/test_peepholer.py @@ -409,21 +409,6 @@ class TestTranforms(BytecodeTestCase): self.assertLessEqual(len(returns), 6) self.check_lnotab(f) - def test_elim_jump_after_return2(self): - # Eliminate dead code: jumps immediately after returns can't be reached - def f(cond1, cond2): - while 1: - if cond1: return 4 - self.assertNotInBytecode(f, 'JUMP_FORWARD') - # There should be one jump for the while loop. - jumps = [instr for instr in dis.get_instructions(f) - if 'JUMP' in instr.opname] - self.assertEqual(len(jumps), 1) - returns = [instr for instr in dis.get_instructions(f) - if instr.opname == 'RETURN_VALUE'] - self.assertLessEqual(len(returns), 2) - self.check_lnotab(f) - def test_make_function_doesnt_bail(self): def f(): def g()->1+1: diff --git a/Lib/test/test_peg_generator/test_c_parser.py b/Lib/test/test_peg_generator/test_c_parser.py index 0dffedca78..67bb851211 100644 --- a/Lib/test/test_peg_generator/test_c_parser.py +++ b/Lib/test/test_peg_generator/test_c_parser.py @@ -1,3 +1,4 @@ +import sysconfig import textwrap import unittest from distutils.tests.support import TempdirManager @@ -8,6 +9,11 @@ from test import support from test.support import os_helper from test.support.script_helper import assert_python_ok +_py_cflags_nodist = sysconfig.get_config_var('PY_CFLAGS_NODIST') +_pgo_flag = sysconfig.get_config_var('PGO_PROF_USE_FLAG') +if _pgo_flag and _py_cflags_nodist and _pgo_flag in _py_cflags_nodist: + raise unittest.SkipTest("peg_generator test disabled under PGO build") + test_tools.skip_if_missing("peg_generator") with test_tools.imports_under_tool("peg_generator"): from pegen.grammar_parser import GeneratedParser as GrammarParser diff --git a/Lib/test/test_peg_generator/test_grammar_validator.py b/Lib/test/test_peg_generator/test_grammar_validator.py new file mode 100644 index 0000000000..2e72ff8df2 --- /dev/null +++ b/Lib/test/test_peg_generator/test_grammar_validator.py @@ -0,0 +1,51 @@ +import unittest +from test import test_tools + +test_tools.skip_if_missing('peg_generator') +with test_tools.imports_under_tool('peg_generator'): + from pegen.grammar_parser import GeneratedParser as GrammarParser + from pegen.validator import SubRuleValidator, ValidationError + from pegen.testutil import parse_string + from pegen.grammar import Grammar + + +class TestPegen(unittest.TestCase): + def test_rule_with_no_collision(self) -> None: + grammar_source = """ + start: bad_rule + sum: + | NAME '-' NAME + | NAME '+' NAME + """ + grammar: Grammar = parse_string(grammar_source, GrammarParser) + validator = SubRuleValidator(grammar) + for rule_name, rule in grammar.rules.items(): + validator.validate_rule(rule_name, rule) + + def test_rule_with_simple_collision(self) -> None: + grammar_source = """ + start: bad_rule + sum: + | NAME '+' NAME + | NAME '+' NAME ';' + """ + grammar: Grammar = parse_string(grammar_source, GrammarParser) + validator = SubRuleValidator(grammar) + with self.assertRaises(ValidationError): + for rule_name, rule in grammar.rules.items(): + validator.validate_rule(rule_name, rule) + + def test_rule_with_collision_after_some_other_rules(self) -> None: + grammar_source = """ + start: bad_rule + sum: + | NAME '+' NAME + | NAME '*' NAME ';' + | NAME '-' NAME + | NAME '+' NAME ';' + """ + grammar: Grammar = parse_string(grammar_source, GrammarParser) + validator = SubRuleValidator(grammar) + with self.assertRaises(ValidationError): + for rule_name, rule in grammar.rules.items(): + validator.validate_rule(rule_name, rule) diff --git a/Lib/test/test_pipes.py b/Lib/test/test_pipes.py index 7d8cd54ba0..6a13b36d1c 100644 --- a/Lib/test/test_pipes.py +++ b/Lib/test/test_pipes.py @@ -3,13 +3,16 @@ import os import string import unittest import shutil -from test.support import run_unittest, reap_children +from test.support import run_unittest, reap_children, unix_shell from test.support.os_helper import TESTFN, unlink if os.name != 'posix': raise unittest.SkipTest('pipes module only works on posix') +if not (unix_shell and os.path.exists(unix_shell)): + raise unittest.SkipTest('pipes module requires a shell') + TESTFN2 = TESTFN + "2" # tr a-z A-Z is not portable, so make the ranges explicit diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index b5d21e5461..2c6fbee8b6 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -8,12 +8,70 @@ from unittest import mock from test import support from test.support import os_helper +FEDORA_OS_RELEASE = """\ +NAME=Fedora +VERSION="32 (Thirty Two)" +ID=fedora +VERSION_ID=32 +VERSION_CODENAME="" +PLATFORM_ID="platform:f32" +PRETTY_NAME="Fedora 32 (Thirty Two)" +ANSI_COLOR="0;34" +LOGO=fedora-logo-icon +CPE_NAME="cpe:/o:fedoraproject:fedora:32" +HOME_URL="https://fedoraproject.org/" +DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f32/system-administrators-guide/" +SUPPORT_URL="https://fedoraproject.org/wiki/Communicating_and_getting_help" +BUG_REPORT_URL="https://bugzilla.redhat.com/" +REDHAT_BUGZILLA_PRODUCT="Fedora" +REDHAT_BUGZILLA_PRODUCT_VERSION=32 +REDHAT_SUPPORT_PRODUCT="Fedora" +REDHAT_SUPPORT_PRODUCT_VERSION=32 +PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy" +""" + +UBUNTU_OS_RELEASE = """\ +NAME="Ubuntu" +VERSION="20.04.1 LTS (Focal Fossa)" +ID=ubuntu +ID_LIKE=debian +PRETTY_NAME="Ubuntu 20.04.1 LTS" +VERSION_ID="20.04" +HOME_URL="https://www.ubuntu.com/" +SUPPORT_URL="https://help.ubuntu.com/" +BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" +PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" +VERSION_CODENAME=focal +UBUNTU_CODENAME=focal +""" + +TEST_OS_RELEASE = r""" +# test data +ID_LIKE="egg spam viking" +EMPTY= +# comments and empty lines are ignored + +SINGLE_QUOTE='single' +EMPTY_SINGLE='' +DOUBLE_QUOTE="double" +EMPTY_DOUBLE="" +QUOTES="double\'s" +SPECIALS="\$\`\\\'\"" +# invalid lines +=invalid += +INVALID +IN-VALID=value +IN VALID=value +""" + class PlatformTest(unittest.TestCase): def clear_caches(self): platform._platform_cache.clear() platform._sys_version_cache.clear() platform._uname_cache = None + platform._os_release_cache = None def test_architecture(self): res = platform.architecture() @@ -238,7 +296,10 @@ class PlatformTest(unittest.TestCase): # On Snow Leopard, sw_vers reports 10.6.0 as 10.6 if len_diff > 0: expect_list.extend(['0'] * len_diff) - self.assertEqual(result_list, expect_list) + # For compatibility with older binaries, macOS 11.x may report + # itself as '10.16' rather than '11.x.y'. + if result_list != ['10', '16']: + self.assertEqual(result_list, expect_list) # res[1] claims to contain # (version, dev_stage, non_release_version) @@ -246,7 +307,7 @@ class PlatformTest(unittest.TestCase): self.assertEqual(res[1], ('', '', '')) if sys.byteorder == 'little': - self.assertIn(res[2], ('i386', 'x86_64')) + self.assertIn(res[2], ('i386', 'x86_64', 'arm64')) else: self.assertEqual(res[2], 'PowerPC') @@ -379,6 +440,54 @@ class PlatformTest(unittest.TestCase): self.assertEqual(platform.platform(terse=1), expected_terse) self.assertEqual(platform.platform(), expected) + def test_freedesktop_os_release(self): + self.addCleanup(self.clear_caches) + self.clear_caches() + + if any(os.path.isfile(fn) for fn in platform._os_release_candidates): + info = platform.freedesktop_os_release() + self.assertIn("NAME", info) + self.assertIn("ID", info) + + info["CPYTHON_TEST"] = "test" + self.assertNotIn( + "CPYTHON_TEST", + platform.freedesktop_os_release() + ) + else: + with self.assertRaises(OSError): + platform.freedesktop_os_release() + + def test_parse_os_release(self): + info = platform._parse_os_release(FEDORA_OS_RELEASE.splitlines()) + self.assertEqual(info["NAME"], "Fedora") + self.assertEqual(info["ID"], "fedora") + self.assertNotIn("ID_LIKE", info) + self.assertEqual(info["VERSION_CODENAME"], "") + + info = platform._parse_os_release(UBUNTU_OS_RELEASE.splitlines()) + self.assertEqual(info["NAME"], "Ubuntu") + self.assertEqual(info["ID"], "ubuntu") + self.assertEqual(info["ID_LIKE"], "debian") + self.assertEqual(info["VERSION_CODENAME"], "focal") + + info = platform._parse_os_release(TEST_OS_RELEASE.splitlines()) + expected = { + "ID": "linux", + "NAME": "Linux", + "PRETTY_NAME": "Linux", + "ID_LIKE": "egg spam viking", + "EMPTY": "", + "DOUBLE_QUOTE": "double", + "EMPTY_DOUBLE": "", + "SINGLE_QUOTE": "single", + "EMPTY_SINGLE": "", + "QUOTES": "double's", + "SPECIALS": "$`\\'\"", + } + self.assertEqual(info, expected) + self.assertEqual(len(info["SPECIALS"]), 5) + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_popen.py b/Lib/test/test_popen.py index ab1bc77655..cac2f6177f 100644 --- a/Lib/test/test_popen.py +++ b/Lib/test/test_popen.py @@ -7,6 +7,9 @@ import unittest from test import support import os, sys +if not hasattr(os, 'popen'): + raise unittest.SkipTest("need os.popen()") + # Test that command-lines get down as we expect. # To do this we execute: # python -c "import sys;print(sys.argv)" {rest_of_commandline} diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py index 2ac345ddd6..548868362a 100644 --- a/Lib/test/test_poplib.py +++ b/Lib/test/test_poplib.py @@ -501,7 +501,7 @@ class TestTimeouts(TestCase): conn, addr = serv.accept() conn.send(b"+ Hola mundo\n") conn.close() - except socket.timeout: + except TimeoutError: pass finally: serv.close() diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index f57c88234b..588c86994b 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -642,12 +642,17 @@ class PosixTester(unittest.TestCase): @unittest.skipUnless(hasattr(posix, 'mkfifo'), "don't have mkfifo()") def test_mkfifo(self): - os_helper.unlink(os_helper.TESTFN) + if sys.platform == "vxworks": + fifo_path = os.path.join("/fifos/", os_helper.TESTFN) + else: + fifo_path = os_helper.TESTFN + os_helper.unlink(fifo_path) + self.addCleanup(os_helper.unlink, fifo_path) try: - posix.mkfifo(os_helper.TESTFN, stat.S_IRUSR | stat.S_IWUSR) + posix.mkfifo(fifo_path, stat.S_IRUSR | stat.S_IWUSR) except PermissionError as e: self.skipTest('posix.mkfifo(): %s' % e) - self.assertTrue(stat.S_ISFIFO(posix.stat(os_helper.TESTFN).st_mode)) + self.assertTrue(stat.S_ISFIFO(posix.stat(fifo_path).st_mode)) @unittest.skipUnless(hasattr(posix, 'mknod') and hasattr(stat, 'S_IFIFO'), "don't have mknod()/S_IFIFO") @@ -719,11 +724,20 @@ class PosixTester(unittest.TestCase): chown_func(first_param, uid, -1) check_stat(uid, gid) - if uid == 0: + if sys.platform == "vxworks": + # On VxWorks, root user id is 1 and 0 means no login user: + # both are super users. + is_root = (uid in (0, 1)) + else: + is_root = (uid == 0) + if is_root: # Try an amusingly large uid/gid to make sure we handle # large unsigned values. (chown lets you use any # uid/gid you like, even if they aren't defined.) # + # On VxWorks uid_t is defined as unsigned short. A big + # value greater than 65535 will result in underflow error. + # # This problem keeps coming up: # http://bugs.python.org/issue1747858 # http://bugs.python.org/issue4591 @@ -733,7 +747,7 @@ class PosixTester(unittest.TestCase): # This part of the test only runs when run as root. # Only scary people run their tests as root. - big_value = 2**31 + big_value = (2**31 if sys.platform != "vxworks" else 2**15) chown_func(first_param, big_value, big_value) check_stat(big_value, big_value) chown_func(first_param, -1, -1) @@ -1040,6 +1054,7 @@ class PosixTester(unittest.TestCase): @unittest.skipUnless(hasattr(os, 'getegid'), "test needs os.getegid()") + @unittest.skipUnless(hasattr(os, 'popen'), "test needs os.popen()") def test_getgroups(self): with os.popen('id -G 2>/dev/null') as idg: groups = idg.read().strip() @@ -1056,7 +1071,7 @@ class PosixTester(unittest.TestCase): if sys.platform == 'darwin': import sysconfig dt = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET') or '10.0' - if tuple(int(n) for n in dt.split('.')[0:2]) < (10, 6): + if tuple(int(n) for n in str(dt).split('.')[0:2]) < (10, 6): raise unittest.SkipTest("getgroups(2) is broken prior to 10.6") # 'id -G' and 'os.getgroups()' should return the same @@ -1089,7 +1104,8 @@ class PosixTester(unittest.TestCase): finally: posix.close(f) - @unittest.skipUnless(os.chown in os.supports_dir_fd, "test needs dir_fd support in os.chown()") + @unittest.skipUnless(hasattr(os, 'chown') and (os.chown in os.supports_dir_fd), + "test needs dir_fd support in os.chown()") def test_chown_dir_fd(self): os_helper.unlink(os_helper.TESTFN) os_helper.create_empty_file(os_helper.TESTFN) @@ -1184,7 +1200,9 @@ class PosixTester(unittest.TestCase): posix.close(f) os_helper.rmtree(os_helper.TESTFN + 'dir') - @unittest.skipUnless((os.mknod in os.supports_dir_fd) and hasattr(stat, 'S_IFIFO'), + @unittest.skipUnless(hasattr(os, 'mknod') + and (os.mknod in os.supports_dir_fd) + and hasattr(stat, 'S_IFIFO'), "test requires both stat.S_IFIFO and dir_fd support for os.mknod()") def test_mknod_dir_fd(self): # Test using mknodat() to create a FIFO (the only use specified @@ -1217,7 +1235,8 @@ class PosixTester(unittest.TestCase): posix.close(a) posix.close(b) - @unittest.skipUnless(os.readlink in os.supports_dir_fd, "test needs dir_fd support in os.readlink()") + @unittest.skipUnless(hasattr(os, 'readlink') and (os.readlink in os.supports_dir_fd), + "test needs dir_fd support in os.readlink()") def test_readlink_dir_fd(self): os.symlink(os_helper.TESTFN, os_helper.TESTFN + 'link') f = posix.open(posix.getcwd(), posix.O_RDONLY) @@ -1925,6 +1944,233 @@ class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin): assert_python_ok(*args, PATH=path) +@unittest.skipUnless(sys.platform == "darwin", "test weak linking on macOS") +class TestPosixWeaklinking(unittest.TestCase): + # These test cases verify that weak linking support on macOS works + # as expected. These cases only test new behaviour introduced by weak linking, + # regular behaviour is tested by the normal test cases. + # + # See the section on Weak Linking in Mac/README.txt for more information. + def setUp(self): + import sysconfig + import platform + + config_vars = sysconfig.get_config_vars() + self.available = { nm for nm in config_vars if nm.startswith("HAVE_") and config_vars[nm] } + self.mac_ver = tuple(int(part) for part in platform.mac_ver()[0].split(".")) + + def _verify_available(self, name): + if name not in self.available: + raise unittest.SkipTest(f"{name} not weak-linked") + + def test_pwritev(self): + self._verify_available("HAVE_PWRITEV") + if self.mac_ver >= (10, 16): + self.assertTrue(hasattr(os, "pwritev"), "os.pwritev is not available") + self.assertTrue(hasattr(os, "preadv"), "os.readv is not available") + + else: + self.assertFalse(hasattr(os, "pwritev"), "os.pwritev is available") + self.assertFalse(hasattr(os, "preadv"), "os.readv is available") + + def test_stat(self): + self._verify_available("HAVE_FSTATAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_FSTATAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_FSTATAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.stat("file", dir_fd=0) + + def test_access(self): + self._verify_available("HAVE_FACCESSAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_FACCESSAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_FACCESSAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.access("file", os.R_OK, dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "follow_symlinks unavailable"): + os.access("file", os.R_OK, follow_symlinks=False) + + with self.assertRaisesRegex(NotImplementedError, "effective_ids unavailable"): + os.access("file", os.R_OK, effective_ids=True) + + def test_chmod(self): + self._verify_available("HAVE_FCHMODAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_FCHMODAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_FCHMODAT", posix._have_functions) + self.assertIn("HAVE_LCHMOD", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.chmod("file", 0o644, dir_fd=0) + + def test_chown(self): + self._verify_available("HAVE_FCHOWNAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_FCHOWNAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_FCHOWNAT", posix._have_functions) + self.assertIn("HAVE_LCHOWN", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.chown("file", 0, 0, dir_fd=0) + + def test_link(self): + self._verify_available("HAVE_LINKAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_LINKAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_LINKAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "src_dir_fd unavailable"): + os.link("source", "target", src_dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "dst_dir_fd unavailable"): + os.link("source", "target", dst_dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "src_dir_fd unavailable"): + os.link("source", "target", src_dir_fd=0, dst_dir_fd=0) + + # issue 41355: !HAVE_LINKAT code path ignores the follow_symlinks flag + with os_helper.temp_dir() as base_path: + link_path = os.path.join(base_path, "link") + target_path = os.path.join(base_path, "target") + source_path = os.path.join(base_path, "source") + + with open(source_path, "w") as fp: + fp.write("data") + + os.symlink("target", link_path) + + # Calling os.link should fail in the link(2) call, and + # should not reject *follow_symlinks* (to match the + # behaviour you'd get when building on a platform without + # linkat) + with self.assertRaises(FileExistsError): + os.link(source_path, link_path, follow_symlinks=True) + + with self.assertRaises(FileExistsError): + os.link(source_path, link_path, follow_symlinks=False) + + + def test_listdir_scandir(self): + self._verify_available("HAVE_FDOPENDIR") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_FDOPENDIR", posix._have_functions) + + else: + self.assertNotIn("HAVE_FDOPENDIR", posix._have_functions) + + with self.assertRaisesRegex(TypeError, "listdir: path should be string, bytes, os.PathLike or None, not int"): + os.listdir(0) + + with self.assertRaisesRegex(TypeError, "scandir: path should be string, bytes, os.PathLike or None, not int"): + os.scandir(0) + + def test_mkdir(self): + self._verify_available("HAVE_MKDIRAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_MKDIRAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_MKDIRAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.mkdir("dir", dir_fd=0) + + def test_rename_replace(self): + self._verify_available("HAVE_RENAMEAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_RENAMEAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_RENAMEAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"): + os.rename("a", "b", src_dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"): + os.rename("a", "b", dst_dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"): + os.replace("a", "b", src_dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "src_dir_fd and dst_dir_fd unavailable"): + os.replace("a", "b", dst_dir_fd=0) + + def test_unlink_rmdir(self): + self._verify_available("HAVE_UNLINKAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_UNLINKAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_UNLINKAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.unlink("path", dir_fd=0) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.rmdir("path", dir_fd=0) + + def test_open(self): + self._verify_available("HAVE_OPENAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_OPENAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_OPENAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.open("path", os.O_RDONLY, dir_fd=0) + + def test_readlink(self): + self._verify_available("HAVE_READLINKAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_READLINKAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_READLINKAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.readlink("path", dir_fd=0) + + def test_symlink(self): + self._verify_available("HAVE_SYMLINKAT") + if self.mac_ver >= (10, 10): + self.assertIn("HAVE_SYMLINKAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_SYMLINKAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.symlink("a", "b", dir_fd=0) + + def test_utime(self): + self._verify_available("HAVE_FUTIMENS") + self._verify_available("HAVE_UTIMENSAT") + if self.mac_ver >= (10, 13): + self.assertIn("HAVE_FUTIMENS", posix._have_functions) + self.assertIn("HAVE_UTIMENSAT", posix._have_functions) + + else: + self.assertNotIn("HAVE_FUTIMENS", posix._have_functions) + self.assertNotIn("HAVE_UTIMENSAT", posix._have_functions) + + with self.assertRaisesRegex(NotImplementedError, "dir_fd unavailable"): + os.utime("path", dir_fd=0) + + def test_main(): try: support.run_unittest( @@ -1932,6 +2178,7 @@ def test_main(): PosixGroupsTester, TestPosixSpawn, TestPosixSpawnP, + TestPosixWeaklinking ) finally: support.reap_children() diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index 42fd8ef8b1..e18d01f463 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -1,5 +1,6 @@ import os import posixpath +import sys import unittest from posixpath import realpath, abspath, dirname, basename from test import test_genericpath @@ -262,6 +263,8 @@ class PosixPathTest(unittest.TestCase): self.assertEqual(posixpath.expanduser("~/"), "/") self.assertEqual(posixpath.expanduser("~/foo"), "/foo") + @unittest.skipIf(sys.platform == "vxworks", + "no home directory on VxWorks") def test_expanduser_pwd(self): pwd = import_helper.import_module('pwd') diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py index 8ee18e8fef..c4a8578a9f 100644 --- a/Lib/test/test_pprint.py +++ b/Lib/test/test_pprint.py @@ -453,12 +453,23 @@ AdvancedNamespace(the=0, dog=8)""") def test_subclassing(self): + # length(repr(obj)) > width o = {'names with spaces': 'should be presented using repr()', 'others.should.not.be': 'like.this'} exp = """\ {'names with spaces': 'should be presented using repr()', others.should.not.be: like.this}""" - self.assertEqual(DottedPrettyPrinter().pformat(o), exp) + + dotted_printer = DottedPrettyPrinter() + self.assertEqual(dotted_printer.pformat(o), exp) + + # length(repr(obj)) < width + o1 = ['with space'] + exp1 = "['with space']" + self.assertEqual(dotted_printer.pformat(o1), exp1) + o2 = ['without.space'] + exp2 = "[without.space]" + self.assertEqual(dotted_printer.pformat(o2), exp2) def test_set_reprs(self): self.assertEqual(pprint.pformat(set()), 'set()') diff --git a/Lib/test/test_pty.py b/Lib/test/test_pty.py index 7ca0557800..190d8d787a 100644 --- a/Lib/test/test_pty.py +++ b/Lib/test/test_pty.py @@ -14,9 +14,21 @@ import socket import io # readline import unittest +import struct +import tty +import fcntl +import warnings + TEST_STRING_1 = b"I wish to buy a fish license.\n" TEST_STRING_2 = b"For my pet fish, Eric.\n" +try: + _TIOCGWINSZ = tty.TIOCGWINSZ + _TIOCSWINSZ = tty.TIOCSWINSZ + _HAVE_WINSZ = True +except AttributeError: + _HAVE_WINSZ = False + if verbose: def debug(msg): print(msg) @@ -60,6 +72,21 @@ def _readline(fd): reader = io.FileIO(fd, mode='rb', closefd=False) return reader.readline() +def expectedFailureIfStdinIsTTY(fun): + # avoid isatty() + try: + tty.tcgetattr(pty.STDIN_FILENO) + return unittest.expectedFailure(fun) + except tty.error: + pass + return fun + +def _get_term_winsz(fd): + s = struct.pack("HHHH", 0, 0, 0, 0) + return fcntl.ioctl(fd, _TIOCGWINSZ, s) + +def _set_term_winsz(fd, winsz): + fcntl.ioctl(fd, _TIOCSWINSZ, winsz) # Marginal testing of pty suite. Cannot do extensive 'do or fail' testing @@ -78,6 +105,20 @@ class PtyTest(unittest.TestCase): self.addCleanup(signal.alarm, 0) signal.alarm(10) + # Save original stdin window size + self.stdin_rows = None + self.stdin_cols = None + if _HAVE_WINSZ: + try: + stdin_dim = os.get_terminal_size(pty.STDIN_FILENO) + self.stdin_rows = stdin_dim.lines + self.stdin_cols = stdin_dim.columns + old_stdin_winsz = struct.pack("HHHH", self.stdin_rows, + self.stdin_cols, 0, 0) + self.addCleanup(_set_term_winsz, pty.STDIN_FILENO, old_stdin_winsz) + except OSError: + pass + def handle_sig(self, sig, frame): self.fail("isatty hung") @@ -86,26 +127,70 @@ class PtyTest(unittest.TestCase): # bpo-38547: if the process is the session leader, os.close(master_fd) # of "master_fd, slave_name = pty.master_open()" raises SIGHUP # signal: just ignore the signal. + # + # NOTE: the above comment is from an older version of the test; + # master_open() is not being used anymore. pass - def test_basic(self): + @expectedFailureIfStdinIsTTY + def test_openpty(self): try: - debug("Calling master_open()") - master_fd, slave_name = pty.master_open() - debug("Got master_fd '%d', slave_name '%s'" % - (master_fd, slave_name)) - debug("Calling slave_open(%r)" % (slave_name,)) - slave_fd = pty.slave_open(slave_name) - debug("Got slave_fd '%d'" % slave_fd) + mode = tty.tcgetattr(pty.STDIN_FILENO) + except tty.error: + # not a tty or bad/closed fd + debug("tty.tcgetattr(pty.STDIN_FILENO) failed") + mode = None + + new_stdin_winsz = None + if self.stdin_rows != None and self.stdin_cols != None: + try: + # Modify pty.STDIN_FILENO window size; we need to + # check if pty.openpty() is able to set pty slave + # window size accordingly. + debug("Setting pty.STDIN_FILENO window size") + debug(f"original size: (rows={self.stdin_rows}, cols={self.stdin_cols})") + target_stdin_rows = self.stdin_rows + 1 + target_stdin_cols = self.stdin_cols + 1 + debug(f"target size: (rows={target_stdin_rows}, cols={target_stdin_cols})") + target_stdin_winsz = struct.pack("HHHH", target_stdin_rows, + target_stdin_cols, 0, 0) + _set_term_winsz(pty.STDIN_FILENO, target_stdin_winsz) + + # Were we able to set the window size + # of pty.STDIN_FILENO successfully? + new_stdin_winsz = _get_term_winsz(pty.STDIN_FILENO) + self.assertEqual(new_stdin_winsz, target_stdin_winsz, + "pty.STDIN_FILENO window size unchanged") + except OSError: + warnings.warn("Failed to set pty.STDIN_FILENO window size") + pass + + try: + debug("Calling pty.openpty()") + try: + master_fd, slave_fd = pty.openpty(mode, new_stdin_winsz) + except TypeError: + master_fd, slave_fd = pty.openpty() + debug(f"Got master_fd '{master_fd}', slave_fd '{slave_fd}'") except OSError: # " An optional feature could not be imported " ... ? raise unittest.SkipTest("Pseudo-terminals (seemingly) not functional.") - self.assertTrue(os.isatty(slave_fd), 'slave_fd is not a tty') + self.assertTrue(os.isatty(slave_fd), "slave_fd is not a tty") + + if mode: + self.assertEqual(tty.tcgetattr(slave_fd), mode, + "openpty() failed to set slave termios") + if new_stdin_winsz: + self.assertEqual(_get_term_winsz(slave_fd), new_stdin_winsz, + "openpty() failed to set slave window size") # Solaris requires reading the fd before anything is returned. # My guess is that since we open and close the slave fd # in master_open(), we need to read the EOF. + # + # NOTE: the above comment is from an older version of the test; + # master_open() is not being used anymore. # Ensure the fd is non-blocking in case there's nothing to read. blocking = os.get_blocking(master_fd) @@ -222,8 +307,22 @@ class PtyTest(unittest.TestCase): os.close(master_fd) - # pty.fork() passed. + def test_master_read(self): + debug("Calling pty.openpty()") + master_fd, slave_fd = pty.openpty() + debug(f"Got master_fd '{master_fd}', slave_fd '{slave_fd}'") + debug("Closing slave_fd") + os.close(slave_fd) + + debug("Reading from master_fd") + try: + data = os.read(master_fd, 1) + except OSError: # Linux + data = b"" + + os.close(master_fd) + self.assertEqual(data, b"") class SmallPtyTests(unittest.TestCase): """These tests don't spawn children or hang.""" @@ -262,8 +361,9 @@ class SmallPtyTests(unittest.TestCase): self.files.extend(socketpair) return socketpair - def _mock_select(self, rfds, wfds, xfds): + def _mock_select(self, rfds, wfds, xfds, timeout=0): # This will raise IndexError when no more expected calls exist. + # This ignores the timeout self.assertEqual(self.select_rfds_lengths.pop(0), len(rfds)) return self.select_rfds_results.pop(0), [], [] diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py index 869799cfa9..2c7afa994f 100644 --- a/Lib/test/test_pyclbr.py +++ b/Lib/test/test_pyclbr.py @@ -150,9 +150,6 @@ class PyclbrTest(TestCase): self.checkModule('difflib', ignore=("Match",)) def test_decorators(self): - # XXX: See comment in pyclbr_input.py for a test that would fail - # if it were not commented out. - # self.checkModule('test.pyclbr_input', ignore=['om']) def test_nested(self): @@ -160,10 +157,10 @@ class PyclbrTest(TestCase): # Set arguments for descriptor creation and _creat_tree call. m, p, f, t, i = 'test', '', 'test.py', {}, None source = dedent("""\ - def f0: + def f0(): def f1(a,b,c): def f2(a=1, b=2, c=3): pass - return f1(a,b,d) + return f1(a,b,d) class c1: pass class C0: "Test class." diff --git a/Lib/test/test_random.py b/Lib/test/test_random.py index 0c1fdeec99..e7f911d128 100644 --- a/Lib/test/test_random.py +++ b/Lib/test/test_random.py @@ -11,7 +11,7 @@ from functools import partial from math import log, exp, pi, fsum, sin, factorial from test import support from fractions import Fraction -from collections import Counter +from collections import abc, Counter class TestBasicOps: # Superclass with tests common to all generators. @@ -163,6 +163,22 @@ class TestBasicOps: population = {10, 20, 30, 40, 50, 60, 70} self.gen.sample(population, k=5) + def test_sample_on_seqsets(self): + class SeqSet(abc.Sequence, abc.Set): + def __init__(self, items): + self._items = items + + def __len__(self): + return len(self._items) + + def __getitem__(self, index): + return self._items[index] + + population = SeqSet([2, 4, 1, 3]) + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + self.gen.sample(population, k=2) + def test_sample_with_counts(self): sample = self.gen.sample @@ -398,6 +414,15 @@ class TestBasicOps: r = _random.Random() self.assertRaises(TypeError, pickle.dumps, r, proto) + @test.support.cpython_only + def test_bug_42008(self): + # _random.Random should call seed with first element of arg tuple + import _random + r1 = _random.Random() + r1.seed(8675309) + r2 = _random.Random(8675309) + self.assertEqual(r1.random(), r2.random()) + def test_bug_1727780(self): # verify that version-2-pickles can be loaded # fine, whether they are created on 32-bit or 64-bit diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index 1bfbcb853c..c1d02cfaf0 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -2197,6 +2197,10 @@ class ImplementationTest(unittest.TestCase): self.assertEqual(f("ababba"), [0, 0, 1, 2, 0, 1]) self.assertEqual(f("abcabdac"), [0, 0, 0, 1, 2, 0, 1, 0]) + def test_signedness(self): + self.assertGreaterEqual(sre_compile.MAXREPEAT, 0) + self.assertGreaterEqual(sre_compile.MAXGROUPS, 0) + class ExternalTests(unittest.TestCase): diff --git a/Lib/test/test_select.py b/Lib/test/test_select.py index 458998a62f..f63564e6b0 100644 --- a/Lib/test/test_select.py +++ b/Lib/test/test_select.py @@ -1,7 +1,9 @@ import errno import os import select +import subprocess import sys +import textwrap import unittest from test import support @@ -44,17 +46,27 @@ class SelectTestCase(unittest.TestCase): self.assertIsNot(r, x) self.assertIsNot(w, x) + @unittest.skipUnless(hasattr(os, 'popen'), "need os.popen()") def test_select(self): - cmd = 'for i in 0 1 2 3 4 5 6 7 8 9; do echo testing...; sleep 1; done' - with os.popen(cmd) as p: - for tout in (0, 1, 2, 4, 8, 16) + (None,)*10: + code = textwrap.dedent(''' + import time + for i in range(10): + print("testing...", flush=True) + time.sleep(0.050) + ''') + cmd = [sys.executable, '-I', '-c', code] + with subprocess.Popen(cmd, stdout=subprocess.PIPE) as proc: + pipe = proc.stdout + for timeout in (0, 1, 2, 4, 8, 16) + (None,)*10: if support.verbose: - print('timeout =', tout) - rfd, wfd, xfd = select.select([p], [], [], tout) - if (rfd, wfd, xfd) == ([], [], []): + print(f'timeout = {timeout}') + rfd, wfd, xfd = select.select([pipe], [], [], timeout) + self.assertEqual(wfd, []) + self.assertEqual(xfd, []) + if not rfd: continue - if (rfd, wfd, xfd) == ([p], [], []): - line = p.readline() + if rfd == [pipe]: + line = pipe.readline() if support.verbose: print(repr(line)) if not line: @@ -62,7 +74,8 @@ class SelectTestCase(unittest.TestCase): print('EOF') break continue - self.fail('Unexpected return values from select():', rfd, wfd, xfd) + self.fail('Unexpected return values from select():', + rfd, wfd, xfd) # Issue 16230: Crash on select resized list def test_select_mutated(self): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 890f2c7d40..df8dcdcce6 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -280,10 +280,7 @@ class TestRmTree(BaseTest, unittest.TestCase): filename = os.path.join(tmpdir, "tstfile") with self.assertRaises(NotADirectoryError) as cm: shutil.rmtree(filename) - # The reason for this rather odd construct is that Windows sprinkles - # a \*.* at the end of file names. But only sometimes on some buildbots - possible_args = [filename, os.path.join(filename, '*.*')] - self.assertIn(cm.exception.filename, possible_args) + self.assertEqual(cm.exception.filename, filename) self.assertTrue(os.path.exists(filename)) # test that ignore_errors option is honored shutil.rmtree(filename, ignore_errors=True) @@ -296,11 +293,11 @@ class TestRmTree(BaseTest, unittest.TestCase): self.assertIs(errors[0][0], os.scandir) self.assertEqual(errors[0][1], filename) self.assertIsInstance(errors[0][2][1], NotADirectoryError) - self.assertIn(errors[0][2][1].filename, possible_args) + self.assertEqual(errors[0][2][1].filename, filename) self.assertIs(errors[1][0], os.rmdir) self.assertEqual(errors[1][1], filename) self.assertIsInstance(errors[1][2][1], NotADirectoryError) - self.assertIn(errors[1][2][1].filename, possible_args) + self.assertEqual(errors[1][2][1].filename, filename) @unittest.skipIf(sys.platform[:6] == 'cygwin', @@ -683,6 +680,8 @@ class TestCopyTree(BaseTest, unittest.TestCase): # Issue #3002: copyfile and copytree block indefinitely on named pipes @unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()') @os_helper.skip_unless_symlink + @unittest.skipIf(sys.platform == "vxworks", + "fifo requires special path on VxWorks") def test_copytree_named_pipe(self): os.mkdir(TESTFN) try: @@ -1206,6 +1205,8 @@ class TestCopy(BaseTest, unittest.TestCase): # Issue #3002: copyfile and copytree block indefinitely on named pipes @unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()') + @unittest.skipIf(sys.platform == "vxworks", + "fifo requires special path on VxWorks") def test_copyfile_named_pipe(self): try: os.mkfifo(TESTFN) diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index c656790632..6a43fe7037 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -519,16 +519,20 @@ class WakeupSocketSignalTests(unittest.TestCase): else: write.setblocking(False) - # Start with large chunk size to reduce the - # number of send needed to fill the buffer. written = 0 - for chunk_size in (2 ** 16, 2 ** 8, 1): + if sys.platform == "vxworks": + CHUNK_SIZES = (1,) + else: + # Start with large chunk size to reduce the + # number of send needed to fill the buffer. + CHUNK_SIZES = (2 ** 16, 2 ** 8, 1) + for chunk_size in CHUNK_SIZES: chunk = b"x" * chunk_size try: while True: write.send(chunk) written += chunk_size - except (BlockingIOError, socket.timeout): + except (BlockingIOError, TimeoutError): pass print(f"%s bytes written into the socketpair" % written, flush=True) @@ -595,6 +599,7 @@ class WakeupSocketSignalTests(unittest.TestCase): @unittest.skipIf(sys.platform == "win32", "Not valid on Windows") +@unittest.skipUnless(hasattr(signal, 'siginterrupt'), "needs signal.siginterrupt()") class SiginterruptTest(unittest.TestCase): def readpipe_interrupted(self, interrupt): @@ -680,6 +685,8 @@ class SiginterruptTest(unittest.TestCase): @unittest.skipIf(sys.platform == "win32", "Not valid on Windows") +@unittest.skipUnless(hasattr(signal, 'getitimer') and hasattr(signal, 'setitimer'), + "needs signal.getitimer() and signal.setitimer()") class ItimerTest(unittest.TestCase): def setUp(self): self.hndl_called = False diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index 2e70880f56..6060288248 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -36,6 +36,7 @@ if sys.flags.no_site: import site +HAS_USER_SITE = (site.USER_SITE is not None) OLD_SYS_PATH = None @@ -195,6 +196,7 @@ class HelperFunctionsTests(unittest.TestCase): def test__getuserbase(self): self.assertEqual(site._getuserbase(), sysconfig._getuserbase()) + @unittest.skipUnless(HAS_USER_SITE, 'need user site') def test_get_path(self): if sys.platform == 'darwin' and sys._framework: scheme = 'osx_framework_user' @@ -244,6 +246,7 @@ class HelperFunctionsTests(unittest.TestCase): self.assertEqual(rc, 1, "User base not set by PYTHONUSERBASE") + @unittest.skipUnless(HAS_USER_SITE, 'need user site') def test_getuserbase(self): site.USER_BASE = None user_base = site.getuserbase() @@ -261,6 +264,7 @@ class HelperFunctionsTests(unittest.TestCase): self.assertTrue(site.getuserbase().startswith('xoxo'), site.getuserbase()) + @unittest.skipUnless(HAS_USER_SITE, 'need user site') def test_getusersitepackages(self): site.USER_SITE = None site.USER_BASE = None @@ -295,6 +299,7 @@ class HelperFunctionsTests(unittest.TestCase): wanted = os.path.join('xoxo', 'lib', 'site-packages') self.assertEqual(dirs[1], wanted) + @unittest.skipUnless(HAS_USER_SITE, 'need user site') def test_no_home_directory(self): # bpo-10496: getuserbase() and getusersitepackages() must not fail if # the current user has no home directory (if expanduser() returns the diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py index 7816ed3488..91985384ec 100644 --- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -40,7 +40,7 @@ def server(evt, buf, serv): evt.set() try: conn, addr = serv.accept() - except socket.timeout: + except TimeoutError: pass else: n = 500 @@ -193,7 +193,7 @@ def debugging_server(serv, serv_evt, client_evt): n -= 1 - except socket.timeout: + except TimeoutError: pass finally: if not client_evt.is_set(): diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 80638325ba..e4af713b4c 100755 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1611,7 +1611,7 @@ class GeneralModuleTests(unittest.TestCase): if with_timeout: signal.signal(signal.SIGALRM, ok_handler) signal.alarm(1) - self.assertRaises(socket.timeout, c.sendall, + self.assertRaises(TimeoutError, c.sendall, b"x" * support.SOCK_MAX_SIZE) finally: signal.alarm(0) @@ -1738,6 +1738,7 @@ class GeneralModuleTests(unittest.TestCase): @unittest.skipUnless(socket_helper.IPV6_ENABLED, 'IPv6 required for this test.') @unittest.skipIf(sys.platform == 'win32', 'does not work on Windows') @unittest.skipIf(AIX, 'Symbolic scope id does not work') + @unittest.skipUnless(hasattr(socket, 'if_nameindex'), "test needs socket.if_nameindex()") def test_getaddrinfo_ipv6_scopeid_symbolic(self): # Just pick up any network interface (Linux, Mac OS X) (ifindex, test_interface) = socket.if_nameindex()[0] @@ -1770,6 +1771,7 @@ class GeneralModuleTests(unittest.TestCase): @unittest.skipUnless(socket_helper.IPV6_ENABLED, 'IPv6 required for this test.') @unittest.skipIf(sys.platform == 'win32', 'does not work on Windows') @unittest.skipIf(AIX, 'Symbolic scope id does not work') + @unittest.skipUnless(hasattr(socket, 'if_nameindex'), "test needs socket.if_nameindex()") def test_getnameinfo_ipv6_scopeid_symbolic(self): # Just pick up any network interface. (ifindex, test_interface) = socket.if_nameindex()[0] @@ -2966,7 +2968,7 @@ class SendmsgStreamTests(SendmsgTests): try: while True: self.sendmsgToServer([b"a"*512]) - except socket.timeout: + except TimeoutError: pass except OSError as exc: if exc.errno != errno.ENOMEM: @@ -2974,7 +2976,7 @@ class SendmsgStreamTests(SendmsgTests): # bpo-33937 the test randomly fails on Travis CI with # "OSError: [Errno 12] Cannot allocate memory" else: - self.fail("socket.timeout not raised") + self.fail("TimeoutError not raised") finally: self.misc_event.set() @@ -3109,7 +3111,7 @@ class RecvmsgGenericTests(SendrecvmsgBase): # Check that timeout works. try: self.serv_sock.settimeout(0.03) - self.assertRaises(socket.timeout, + self.assertRaises(TimeoutError, self.doRecvmsg, self.serv_sock, len(MSG)) finally: self.misc_event.set() @@ -4827,7 +4829,7 @@ class FileObjectClassTestCase(SocketConnectedTest): self.cli_conn.settimeout(1) self.read_file.read(3) # First read raises a timeout - self.assertRaises(socket.timeout, self.read_file.read, 1) + self.assertRaises(TimeoutError, self.read_file.read, 1) # Second read is disallowed with self.assertRaises(OSError) as ctx: self.read_file.read(1) @@ -5092,7 +5094,7 @@ class NetworkConnectionNoServer(unittest.TestCase): class MockSocket(socket.socket): def connect(self, *args): - raise socket.timeout('timed out') + raise TimeoutError('timed out') @contextlib.contextmanager def mocked_socket_module(self): @@ -5142,13 +5144,13 @@ class NetworkConnectionNoServer(unittest.TestCase): with self.mocked_socket_module(): try: socket.create_connection((HOST, 1234)) - except socket.timeout: + except TimeoutError: pass except OSError as exc: if socket_helper.IPV6_ENABLED or exc.errno != errno.EAFNOSUPPORT: raise else: - self.fail('socket.timeout not raised') + self.fail('TimeoutError not raised') class NetworkConnectionAttributesTest(SocketTCPTest, ThreadableTest): @@ -5250,7 +5252,7 @@ class NetworkConnectionBehaviourTest(SocketTCPTest, ThreadableTest): def _testOutsideTimeout(self): self.cli = sock = socket.create_connection((HOST, self.port), timeout=1) - self.assertRaises(socket.timeout, lambda: sock.recv(5)) + self.assertRaises(TimeoutError, lambda: sock.recv(5)) class TCPTimeoutTest(SocketTCPTest): @@ -5259,7 +5261,7 @@ class TCPTimeoutTest(SocketTCPTest): def raise_timeout(*args, **kwargs): self.serv.settimeout(1.0) self.serv.accept() - self.assertRaises(socket.timeout, raise_timeout, + self.assertRaises(TimeoutError, raise_timeout, "Error generating a timeout exception (TCP)") def testTimeoutZero(self): @@ -5267,7 +5269,7 @@ class TCPTimeoutTest(SocketTCPTest): try: self.serv.settimeout(0.0) foo = self.serv.accept() - except socket.timeout: + except TimeoutError: self.fail("caught timeout instead of error (TCP)") except OSError: ok = True @@ -5292,7 +5294,7 @@ class TCPTimeoutTest(SocketTCPTest): try: signal.alarm(2) # POSIX allows alarm to be up to 1 second early foo = self.serv.accept() - except socket.timeout: + except TimeoutError: self.fail("caught timeout instead of Alarm") except Alarm: pass @@ -5316,7 +5318,7 @@ class UDPTimeoutTest(SocketUDPTest): def raise_timeout(*args, **kwargs): self.serv.settimeout(1.0) self.serv.recv(1024) - self.assertRaises(socket.timeout, raise_timeout, + self.assertRaises(TimeoutError, raise_timeout, "Error generating a timeout exception (UDP)") def testTimeoutZero(self): @@ -5324,7 +5326,7 @@ class UDPTimeoutTest(SocketUDPTest): try: self.serv.settimeout(0.0) foo = self.serv.recv(1024) - except socket.timeout: + except TimeoutError: self.fail("caught timeout instead of error (UDP)") except OSError: ok = True @@ -5341,7 +5343,7 @@ class UDPLITETimeoutTest(SocketUDPLITETest): def raise_timeout(*args, **kwargs): self.serv.settimeout(1.0) self.serv.recv(1024) - self.assertRaises(socket.timeout, raise_timeout, + self.assertRaises(TimeoutError, raise_timeout, "Error generating a timeout exception (UDPLITE)") def testTimeoutZero(self): @@ -5349,7 +5351,7 @@ class UDPLITETimeoutTest(SocketUDPLITETest): try: self.serv.settimeout(0.0) foo = self.serv.recv(1024) - except socket.timeout: + except TimeoutError: self.fail("caught timeout instead of error (UDPLITE)") except OSError: ok = True @@ -5365,6 +5367,8 @@ class TestExceptions(unittest.TestCase): self.assertTrue(issubclass(socket.herror, OSError)) self.assertTrue(issubclass(socket.gaierror, OSError)) self.assertTrue(issubclass(socket.timeout, OSError)) + self.assertIs(socket.error, OSError) + self.assertIs(socket.timeout, TimeoutError) def test_setblocking_invalidfd(self): # Regression test for issue #28471 @@ -6167,7 +6171,7 @@ class SendfileUsingSendTest(ThreadedTCPSocketTest): with socket.create_connection(address) as sock: sock.settimeout(0.01) meth = self.meth_from_sock(sock) - self.assertRaises(socket.timeout, meth, file) + self.assertRaises(TimeoutError, meth, file) def testWithTimeoutTriggeredSend(self): conn = self.accept_conn() diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 26eec969a8..67850c34e0 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -391,7 +391,7 @@ class BasicSocketTests(unittest.TestCase): ssl.RAND_add(b"this is a random bytes object", 75.0) ssl.RAND_add(bytearray(b"this is a random bytearray object"), 75.0) - @unittest.skipUnless(os.name == 'posix', 'requires posix') + @unittest.skipUnless(hasattr(os, 'fork'), 'need os.fork') def test_random_fork(self): status = ssl.RAND_status() if not status: @@ -2574,7 +2574,7 @@ class ThreadedEchoServer(threading.Thread): handler = self.ConnectionHandler(self, newconn, connaddr) handler.start() handler.join() - except socket.timeout: + except TimeoutError: pass except KeyboardInterrupt: self.stop() @@ -3691,7 +3691,7 @@ class ThreadedTests(unittest.TestCase): c.settimeout(0.2) c.connect((host, port)) # Will attempt handshake and time out - self.assertRaisesRegex(socket.timeout, "timed out", + self.assertRaisesRegex(TimeoutError, "timed out", test_wrap_socket, c) finally: c.close() @@ -3700,7 +3700,7 @@ class ThreadedTests(unittest.TestCase): c = test_wrap_socket(c) c.settimeout(0.2) # Will attempt handshake and time out - self.assertRaisesRegex(socket.timeout, "timed out", + self.assertRaisesRegex(TimeoutError, "timed out", c.connect, (host, port)) finally: c.close() @@ -3854,6 +3854,7 @@ class ThreadedTests(unittest.TestCase): @requires_minimum_version @requires_tls_version('TLSv1_2') + @requires_tls_version('TLSv1') def test_min_max_version_mismatch(self): client_context, server_context, hostname = testing_context() # client 1.0, server 1.2 (mismatch) diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index 83d09e17f9..2e1e2c349c 100644 --- a/Lib/test/test_stat.py +++ b/Lib/test/test_stat.py @@ -2,6 +2,7 @@ import unittest import os import socket import sys +from test.support import os_helper from test.support import socket_helper from test.support.import_helper import import_fresh_module from test.support.os_helper import TESTFN @@ -173,11 +174,16 @@ class TestFilemode: @unittest.skipUnless(hasattr(os, 'mkfifo'), 'os.mkfifo not available') def test_fifo(self): + if sys.platform == "vxworks": + fifo_path = os.path.join("/fifos/", TESTFN) + else: + fifo_path = TESTFN + self.addCleanup(os_helper.unlink, fifo_path) try: - os.mkfifo(TESTFN, 0o700) + os.mkfifo(fifo_path, 0o700) except PermissionError as e: self.skipTest('os.mkfifo(): %s' % e) - st_mode, modestr = self.get_mode() + st_mode, modestr = self.get_mode(fifo_path) self.assertEqual(modestr, 'prwx------') self.assertS_IS("FIFO", st_mode) diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index 997110732a..4b8686b681 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -1599,6 +1599,27 @@ class TestHarmonicMean(NumericTestCase, AverageMixin, UnivariateTypeMixin): actual = self.func(data*2) self.assertApproxEqual(actual, expected) + def test_with_weights(self): + self.assertEqual(self.func([40, 60], [5, 30]), 56.0) # common case + self.assertEqual(self.func([40, 60], + weights=[5, 30]), 56.0) # keyword argument + self.assertEqual(self.func(iter([40, 60]), + iter([5, 30])), 56.0) # iterator inputs + self.assertEqual( + self.func([Fraction(10, 3), Fraction(23, 5), Fraction(7, 2)], [5, 2, 10]), + self.func([Fraction(10, 3)] * 5 + + [Fraction(23, 5)] * 2 + + [Fraction(7, 2)] * 10)) + self.assertEqual(self.func([10], [7]), 10) # n=1 fast path + with self.assertRaises(TypeError): + self.func([1, 2, 3], [1, (), 3]) # non-numeric weight + with self.assertRaises(statistics.StatisticsError): + self.func([1, 2, 3], [1, 2]) # wrong number of weights + with self.assertRaises(statistics.StatisticsError): + self.func([10], [0]) # no non-zero weights + with self.assertRaises(statistics.StatisticsError): + self.func([10, 20], [0, 0]) # no non-zero weights + class TestMedian(NumericTestCase, AverageMixin): # Common tests for median and all median.* functions. diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index e25474abed..70b54f4155 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -204,6 +204,28 @@ class ProcessTestCase(BaseTestCase): input=b'pear') self.assertIn(b'PEAR', output) + def test_check_output_input_none(self): + """input=None has a legacy meaning of input='' on check_output.""" + output = subprocess.check_output( + [sys.executable, "-c", + "import sys; print('XX' if sys.stdin.read() else '')"], + input=None) + self.assertNotIn(b'XX', output) + + def test_check_output_input_none_text(self): + output = subprocess.check_output( + [sys.executable, "-c", + "import sys; print('XX' if sys.stdin.read() else '')"], + input=None, text=True) + self.assertNotIn('XX', output) + + def test_check_output_input_none_universal_newlines(self): + output = subprocess.check_output( + [sys.executable, "-c", + "import sys; print('XX' if sys.stdin.read() else '')"], + input=None, universal_newlines=True) + self.assertNotIn('XX', output) + def test_check_output_stdout_arg(self): # check_output() refuses to accept 'stdout' argument with self.assertRaises(ValueError) as c: @@ -3229,6 +3251,19 @@ class POSIXProcessTestCase(BaseTestCase): # so Popen failed to read it and uses a default returncode instead. self.assertIsNotNone(proc.returncode) + def test_send_signal_race2(self): + # bpo-40550: the process might exist between the returncode check and + # the kill operation + p = subprocess.Popen([sys.executable, '-c', 'exit(1)']) + + # wait for process to exit + while not p.returncode: + p.poll() + + with mock.patch.object(p, 'poll', new=lambda: None): + p.returncode = None + p.send_signal(signal.SIGTERM) + def test_communicate_repeated_call_after_stdout_close(self): proc = subprocess.Popen([sys.executable, '-c', 'import os, time; os.close(1), time.sleep(2)'], diff --git a/Lib/test/test_sundry.py b/Lib/test/test_sundry.py index 04e572c00a..aab074b8a9 100644 --- a/Lib/test/test_sundry.py +++ b/Lib/test/test_sundry.py @@ -9,7 +9,7 @@ import unittest class TestUntestedModules(unittest.TestCase): def test_untested_modules_can_be_imported(self): - untested = ('encodings', 'formatter') + untested = ('encodings',) with warnings_helper.check_warnings(quiet=True): for name in untested: try: diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 91ca1db43a..d8255607dc 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -802,6 +802,13 @@ class SyntaxTestCase(unittest.TestCase): else: self.fail("compile() did not raise SyntaxError") + def test_expression_with_assignment(self): + self._check_error( + "print(end1 + end2 = ' ')", + 'expression cannot contain assignment, perhaps you meant "=="?', + offset=19 + ) + def test_curly_brace_after_primary_raises_immediately(self): self._check_error("f{", "invalid syntax", mode="single") diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 332ed8f550..3860656c18 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -221,7 +221,7 @@ class SysModuleTest(unittest.TestCase): def f(): f() try: - for depth in (10, 25, 50, 75, 100, 250, 1000): + for depth in (50, 75, 100, 250, 1000): try: sys.setrecursionlimit(depth) except RecursionError: @@ -231,17 +231,17 @@ class SysModuleTest(unittest.TestCase): # Issue #5392: test stack overflow after hitting recursion # limit twice - self.assertRaises(RecursionError, f) - self.assertRaises(RecursionError, f) + with self.assertRaises(RecursionError): + f() + with self.assertRaises(RecursionError): + f() finally: sys.setrecursionlimit(oldlimit) @test.support.cpython_only def test_setrecursionlimit_recursion_depth(self): # Issue #25274: Setting a low recursion limit must be blocked if the - # current recursion depth is already higher than the "lower-water - # mark". Otherwise, it may not be possible anymore to - # reset the overflowed flag to 0. + # current recursion depth is already higher than limit. from _testinternalcapi import get_recursion_depth @@ -262,42 +262,10 @@ class SysModuleTest(unittest.TestCase): sys.setrecursionlimit(1000) for limit in (10, 25, 50, 75, 100, 150, 200): - # formula extracted from _Py_RecursionLimitLowerWaterMark() - if limit > 200: - depth = limit - 50 - else: - depth = limit * 3 // 4 - set_recursion_limit_at_depth(depth, limit) + set_recursion_limit_at_depth(limit, limit) finally: sys.setrecursionlimit(oldlimit) - # The error message is specific to CPython - @test.support.cpython_only - def test_recursionlimit_fatalerror(self): - # A fatal error occurs if a second recursion limit is hit when recovering - # from a first one. - code = textwrap.dedent(""" - import sys - - def f(): - try: - f() - except RecursionError: - f() - - sys.setrecursionlimit(%d) - f()""") - with test.support.SuppressCrashReport(): - for i in (50, 1000): - sub = subprocess.Popen([sys.executable, '-c', code % i], - stderr=subprocess.PIPE) - err = sub.communicate()[1] - self.assertTrue(sub.returncode, sub.returncode) - self.assertIn( - b"Fatal Python error: _Py_CheckRecursiveCall: " - b"Cannot recover from stack overflow", - err) - def test_getwindowsversion(self): # Raise SkipTest if sys doesn't have getwindowsversion attribute test.support.get_attribute(sys, "getwindowsversion") @@ -1407,7 +1375,7 @@ class SizeofTest(unittest.TestCase): check(int, s) # class s = vsize(fmt + # PyTypeObject - '3P' # PyAsyncMethods + '4P' # PyAsyncMethods '36P' # PyNumberMethods '3P' # PyMappingMethods '10P' # PySequenceMethods diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index dd4418dd98..50b5672e35 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -53,9 +53,8 @@ basic.events = [(0, 'call'), # following that clause? -# Some constructs like "while 0:", "if 0:" or "if 1:...else:..." are optimized -# away. No code # exists for them, so the line numbers skip directly from -# "del x" to "x = 1". +# Some constructs like "while 0:", "if 0:" or "if 1:...else:..." could be optimized +# away. Make sure that those lines aren't skipped. def arigo_example0(): x = 1 del x @@ -66,6 +65,7 @@ def arigo_example0(): arigo_example0.events = [(0, 'call'), (1, 'line'), (2, 'line'), + (3, 'line'), (5, 'line'), (5, 'return')] @@ -79,6 +79,7 @@ def arigo_example1(): arigo_example1.events = [(0, 'call'), (1, 'line'), (2, 'line'), + (3, 'line'), (5, 'line'), (5, 'return')] @@ -94,6 +95,7 @@ def arigo_example2(): arigo_example2.events = [(0, 'call'), (1, 'line'), (2, 'line'), + (3, 'line'), (4, 'line'), (7, 'line'), (7, 'return')] @@ -220,8 +222,7 @@ ireturn_example.events = [(0, 'call'), (2, 'line'), (3, 'line'), (4, 'line'), - (6, 'line'), - (6, 'return')] + (4, 'return')] # Tight loop with while(1) example (SF #765624) def tightloop_example(): @@ -237,9 +238,13 @@ tightloop_example.events = [(0, 'call'), (1, 'line'), (2, 'line'), (3, 'line'), + (4, 'line'), (5, 'line'), + (4, 'line'), (5, 'line'), + (4, 'line'), (5, 'line'), + (4, 'line'), (5, 'line'), (5, 'exception'), (6, 'line'), @@ -602,6 +607,17 @@ class TraceTestCase(unittest.TestCase): self.compare_events(doit_async.__code__.co_firstlineno, tracer.events, events) + def test_21_repeated_pass(self): + def func(): + pass + pass + + self.run_and_compare(func, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (2, 'return')]) + def test_loop_in_try_except(self): # https://bugs.python.org/issue41670 @@ -619,6 +635,245 @@ class TraceTestCase(unittest.TestCase): (3, 'line'), (3, 'return')]) + def test_try_except_no_exception(self): + + def func(): + try: + 2 + except: + 4 + finally: + 6 + + self.run_and_compare(func, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (6, 'line'), + (6, 'return')]) + + def test_nested_loops(self): + + def func(): + for i in range(2): + for j in range(2): + a = i + j + return a == 1 + + self.run_and_compare(func, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (2, 'line'), + (3, 'line'), + (2, 'line'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (2, 'line'), + (3, 'line'), + (2, 'line'), + (1, 'line'), + (4, 'line'), + (4, 'return')]) + + def test_if_break(self): + + def func(): + seq = [1, 0] + while seq: + n = seq.pop() + if n: + break # line 5 + else: + n = 99 + return n # line 8 + + self.run_and_compare(func, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (5, 'line'), + (8, 'line'), + (8, 'return')]) + + def test_break_through_finally(self): + + def func(): + a, c, d, i = 1, 1, 1, 99 + try: + for i in range(3): + try: + a = 5 + if i > 0: + break # line 7 + a = 8 + finally: + c = 10 + except: + d = 12 # line 12 + assert a == 5 and c == 10 and d == 1 # line 13 + + self.run_and_compare(func, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (5, 'line'), + (6, 'line'), + (8, 'line'), + (10, 'line'), + (3, 'line'), + (4, 'line'), + (5, 'line'), + (6, 'line'), + (7, 'line'), + (10, 'line'), + (13, 'line'), + (13, 'return')]) + + def test_continue_through_finally(self): + + def func(): + a, b, c, d, i = 1, 1, 1, 1, 99 + try: + for i in range(2): + try: + a = 5 + if i > 0: + continue # line 7 + b = 8 + finally: + c = 10 + except: + d = 12 # line 12 + assert (a, b, c, d) == (5, 8, 10, 1) # line 13 + + self.run_and_compare(func, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (5, 'line'), + (6, 'line'), + (8, 'line'), + (10, 'line'), + (3, 'line'), + (4, 'line'), + (5, 'line'), + (6, 'line'), + (7, 'line'), + (10, 'line'), + (3, 'line'), + (13, 'line'), + (13, 'return')]) + + def test_return_through_finally(self): + + def func(): + try: + return 2 + finally: + 4 + + self.run_and_compare(func, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (4, 'line'), + (4, 'return')]) + + def test_try_except_with_wrong_type(self): + + def func(): + try: + 2/0 + except IndexError: + 4 + finally: + return 6 + + self.run_and_compare(func, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (2, 'exception'), + (3, 'line'), + (6, 'line'), + (6, 'return')]) + + def test_break_to_continue1(self): + + def func(): + TRUE = 1 + x = [1] + while x: + x.pop() + while TRUE: + break + continue + + self.run_and_compare(func, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (5, 'line'), + (6, 'line'), + (7, 'line'), + (3, 'line'), + (3, 'return')]) + + def test_break_to_continue2(self): + + def func(): + TRUE = 1 + x = [1] + while x: + x.pop() + while TRUE: + break + else: + continue + + self.run_and_compare(func, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (5, 'line'), + (6, 'line'), + (3, 'line'), + (3, 'return')]) + + def test_break_to_break(self): + + def func(): + TRUE = 1 + while TRUE: + while TRUE: + break + break + + self.run_and_compare(func, + [(0, 'call'), + (1, 'line'), + (2, 'line'), + (3, 'line'), + (4, 'line'), + (5, 'line'), + (5, 'return')]) + class SkipLineEventsTraceTestCase(TraceTestCase): """Repeat the trace tests, but with per-line events skipped""" @@ -766,7 +1021,7 @@ class JumpTracer: if (self.firstLine is None and frame.f_code == self.code and event == 'line'): self.firstLine = frame.f_lineno - 1 - if (event == self.event and self.firstLine and + if (event == self.event and self.firstLine is not None and frame.f_lineno == self.firstLine + self.jumpFrom): f = frame while f is not None and f.f_code != self.code: @@ -980,7 +1235,7 @@ class JumpTestCase(unittest.TestCase): pass output.append(12) - @jump_test(3, 4, [1], (ValueError, 'unreachable')) + @jump_test(3, 4, [1], (ValueError, 'after')) def test_no_jump_infinite_while_loop(output): output.append(1) while True: @@ -1540,7 +1795,7 @@ output.append(4) """, "<fake module>", "exec") class fake_function: __code__ = code - tracer = JumpTracer(fake_function, 2, 0) + tracer = JumpTracer(fake_function, 4, 1) sys.settrace(tracer.trace) namespace = {"output": []} exec(code, namespace) diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index d07d28df60..e279957e26 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -18,6 +18,10 @@ from sysconfig import (get_paths, get_platform, get_config_vars, get_scheme_names, get_config_var, _main) import _osx_support + +HAS_USER_BASE = sysconfig._HAS_USER_BASE + + class TestSysConfig(unittest.TestCase): def setUp(self): @@ -230,9 +234,10 @@ class TestSysConfig(unittest.TestCase): self.assertTrue(os.path.isfile(config_h), config_h) def test_get_scheme_names(self): - wanted = ('nt', 'nt_user', 'osx_framework_user', - 'posix_home', 'posix_prefix', 'posix_user') - self.assertEqual(get_scheme_names(), wanted) + wanted = ['nt', 'posix_home', 'posix_prefix'] + if HAS_USER_BASE: + wanted.extend(['nt_user', 'osx_framework_user', 'posix_user']) + self.assertEqual(get_scheme_names(), tuple(sorted(wanted))) @skip_unless_symlink def test_symlink(self): # Issue 7880 @@ -244,7 +249,8 @@ class TestSysConfig(unittest.TestCase): # Issue #8759: make sure the posix scheme for the users # is similar to the global posix_prefix one base = get_config_var('base') - user = get_config_var('userbase') + if HAS_USER_BASE: + user = get_config_var('userbase') # the global scheme mirrors the distinction between prefix and # exec-prefix but not the user scheme, so we have to adapt the paths # before comparing (issue #9100) @@ -259,8 +265,9 @@ class TestSysConfig(unittest.TestCase): # before comparing global_path = global_path.replace(sys.base_prefix, sys.prefix) base = base.replace(sys.base_prefix, sys.prefix) - user_path = get_path(name, 'posix_user') - self.assertEqual(user_path, global_path.replace(base, user, 1)) + if HAS_USER_BASE: + user_path = get_path(name, 'posix_user') + self.assertEqual(user_path, global_path.replace(base, user, 1)) def test_main(self): # just making sure _main() runs and returns things in the stdout @@ -360,10 +367,12 @@ class TestSysConfig(unittest.TestCase): @unittest.skipIf(sysconfig.get_config_var('EXT_SUFFIX') is None, 'EXT_SUFFIX required for this test') - def test_SO_in_vars(self): + def test_EXT_SUFFIX_in_vars(self): + import _imp vars = sysconfig.get_config_vars() self.assertIsNotNone(vars['SO']) self.assertEqual(vars['SO'], vars['EXT_SUFFIX']) + self.assertEqual(vars['EXT_SUFFIX'], _imp.extension_suffixes()[0]) @unittest.skipUnless(sys.platform == 'linux' and hasattr(sys.implementation, '_multiarch'), diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 7b34d53d21..77ad8305c3 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -1347,10 +1347,10 @@ class WriteTest(WriteTestBase, unittest.TestCase): f.write('something\n') os.symlink(source_file, target_file) with tarfile.open(temparchive, 'w') as tar: - tar.add(source_file) - tar.add(target_file) + tar.add(source_file, arcname="source") + tar.add(target_file, arcname="symlink") # Let's extract it to the location which contains the symlink - with tarfile.open(temparchive) as tar: + with tarfile.open(temparchive, errorlevel=2) as tar: # this should not raise OSError: [Errno 17] File exists try: tar.extractall(path=tempdir) diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index cd2a30e533..5bc4f9bda1 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -1,4 +1,5 @@ import unittest +import locale import re import subprocess import sys @@ -61,6 +62,10 @@ class TclTest(unittest.TestCase): tcl = self.interp self.assertEqual(tcl.eval('set a "a\\0b"'), 'a\x00b') + def test_eval_surrogates_in_result(self): + tcl = self.interp + self.assertIn(tcl.eval(r'set a "<\ud83d\udcbb>"'), '<\U0001f4bb>') + def testEvalException(self): tcl = self.interp self.assertRaises(TclError,tcl.eval,'set a') @@ -133,10 +138,14 @@ class TclTest(unittest.TestCase): def get_integers(self): integers = (0, 1, -1, 2**31-1, -2**31, 2**31, -2**31-1, 2**63-1, -2**63) - # bignum was added in Tcl 8.5, but its support is able only since 8.5.8 - if (get_tk_patchlevel() >= (8, 6, 0, 'final') or - (8, 5, 8) <= get_tk_patchlevel() < (8, 6)): - integers += (2**63, -2**63-1, 2**1000, -2**1000) + # bignum was added in Tcl 8.5, but its support is able only since 8.5.8. + # Actually it is determined at compile time, so using get_tk_patchlevel() + # is not reliable. + # TODO: expose full static version. + if tcl_version >= (8, 5): + v = get_tk_patchlevel() + if v >= (8, 6, 0, 'final') or (8, 5, 8) <= v < (8, 6): + integers += (2**63, -2**63-1, 2**1000, -2**1000) return integers def test_getint(self): @@ -193,29 +202,48 @@ class TclTest(unittest.TestCase): def testEvalFile(self): tcl = self.interp - with open(os_helper.TESTFN, 'w') as f: - self.addCleanup(os_helper.unlink, os_helper.TESTFN) + filename = os_helper.TESTFN_ASCII + self.addCleanup(os_helper.unlink, filename) + with open(filename, 'w') as f: f.write("""set a 1 set b 2 set c [ expr $a + $b ] """) - tcl.evalfile(os_helper.TESTFN) + tcl.evalfile(filename) self.assertEqual(tcl.eval('set a'),'1') self.assertEqual(tcl.eval('set b'),'2') self.assertEqual(tcl.eval('set c'),'3') def test_evalfile_null_in_result(self): tcl = self.interp - with open(os_helper.TESTFN, 'w') as f: - self.addCleanup(os_helper.unlink, os_helper.TESTFN) + filename = os_helper.TESTFN_ASCII + self.addCleanup(os_helper.unlink, filename) + with open(filename, 'w') as f: f.write(""" set a "a\0b" set b "a\\0b" """) - tcl.evalfile(os_helper.TESTFN) + tcl.evalfile(filename) self.assertEqual(tcl.eval('set a'), 'a\x00b') self.assertEqual(tcl.eval('set b'), 'a\x00b') + def test_evalfile_surrogates_in_result(self): + tcl = self.interp + encoding = tcl.call('encoding', 'system') + self.addCleanup(tcl.call, 'encoding', 'system', encoding) + tcl.call('encoding', 'system', 'utf-8') + + filename = os_helper.TESTFN_ASCII + self.addCleanup(os_helper.unlink, filename) + with open(filename, 'wb') as f: + f.write(b""" + set a "<\xed\xa0\xbd\xed\xb2\xbb>" + set b "<\\ud83d\\udcbb>" + """) + tcl.evalfile(filename) + self.assertEqual(tcl.eval('set a'), '<\U0001f4bb>') + self.assertEqual(tcl.eval('set b'), '<\U0001f4bb>') + def testEvalFileException(self): tcl = self.interp filename = "doesnotexists" @@ -438,6 +466,11 @@ class TclTest(unittest.TestCase): self.assertEqual(passValue('str\x00ing\u20ac'), 'str\x00ing\u20ac') self.assertEqual(passValue('str\x00ing\U0001f4bb'), 'str\x00ing\U0001f4bb') + if sys.platform != 'win32': + self.assertEqual(passValue('<\udce2\udc82\udcac>'), + '<\u20ac>') + self.assertEqual(passValue('<\udced\udca0\udcbd\udced\udcb2\udcbb>'), + '<\U0001f4bb>') self.assertEqual(passValue(b'str\x00ing'), b'str\x00ing' if self.wantobjects else 'str\x00ing') self.assertEqual(passValue(b'str\xc0\x80ing'), @@ -497,6 +530,9 @@ class TclTest(unittest.TestCase): check('string\xbd') check('string\u20ac') check('string\U0001f4bb') + if sys.platform != 'win32': + check('<\udce2\udc82\udcac>', '<\u20ac>') + check('<\udced\udca0\udcbd\udced\udcb2\udcbb>', '<\U0001f4bb>') check('') check(b'string', 'string') check(b'string\xe2\x82\xac', 'string\xe2\x82\xac') @@ -540,6 +576,8 @@ class TclTest(unittest.TestCase): ('a \u20ac', ('a', '\u20ac')), ('a \U0001f4bb', ('a', '\U0001f4bb')), (b'a \xe2\x82\xac', ('a', '\u20ac')), + (b'a \xf0\x9f\x92\xbb', ('a', '\U0001f4bb')), + (b'a \xed\xa0\xbd\xed\xb2\xbb', ('a', '\U0001f4bb')), (b'a\xc0\x80b c\xc0\x80d', ('a\x00b', 'c\x00d')), ('a {b c}', ('a', 'b c')), (r'a b\ c', ('a', 'b c')), diff --git a/Lib/test/test_telnetlib.py b/Lib/test/test_telnetlib.py index 7633901c96..8e36051cd0 100644 --- a/Lib/test/test_telnetlib.py +++ b/Lib/test/test_telnetlib.py @@ -16,7 +16,7 @@ def server(evt, serv): try: conn, addr = serv.accept() conn.close() - except socket.timeout: + except TimeoutError: pass finally: serv.close() diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 2f0f3ae094..0a4372ec2d 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -469,6 +469,34 @@ class ThreadTests(BaseTestCase): t = threading.Thread(daemon=True) self.assertTrue(t.daemon) + @unittest.skipUnless(hasattr(os, 'fork'), 'needs os.fork()') + def test_fork_at_exit(self): + # bpo-42350: Calling os.fork() after threading._shutdown() must + # not log an error. + code = textwrap.dedent(""" + import atexit + import os + import sys + from test.support import wait_process + + # Import the threading module to register its "at fork" callback + import threading + + def exit_handler(): + pid = os.fork() + if not pid: + print("child process ok", file=sys.stderr, flush=True) + # child process + else: + wait_process(pid, exitcode=0) + + # exit_handler() will be called after threading._shutdown() + atexit.register(exit_handler) + """) + _, out, err = assert_python_ok("-c", code) + self.assertEqual(out, b'') + self.assertEqual(err.rstrip(), b'child process ok') + @unittest.skipUnless(hasattr(os, 'fork'), 'test needs fork()') def test_dummy_thread_after_fork(self): # Issue #14308: a dummy thread in the active list doesn't mess up @@ -765,6 +793,27 @@ class ThreadTests(BaseTestCase): finally: sys.settrace(old_trace) + def test_gettrace(self): + def noop_trace(frame, event, arg): + # no operation + return noop_trace + old_trace = threading.gettrace() + try: + threading.settrace(noop_trace) + trace_func = threading.gettrace() + self.assertEqual(noop_trace,trace_func) + finally: + threading.settrace(old_trace) + + def test_getprofile(self): + def fn(*args): pass + old_profile = threading.getprofile() + try: + threading.setprofile(fn) + self.assertEqual(fn, threading.getprofile()) + finally: + threading.setprofile(old_profile) + @cpython_only def test_shutdown_locks(self): for daemon in (False, True): @@ -1331,6 +1380,27 @@ class ExceptHookTests(BaseTestCase): 'Exception in threading.excepthook:\n') self.assertEqual(err_str, 'threading_hook failed') + def test_original_excepthook(self): + def run_thread(): + with support.captured_output("stderr") as output: + thread = ThreadRunFail(name="excepthook thread") + thread.start() + thread.join() + return output.getvalue() + + def threading_hook(args): + print("Running a thread failed", file=sys.stderr) + + default_output = run_thread() + with support.swap_attr(threading, 'excepthook', threading_hook): + custom_hook_output = run_thread() + threading.excepthook = threading.__excepthook__ + recovered_output = run_thread() + + self.assertEqual(default_output, recovered_output) + self.assertNotEqual(default_output, custom_hook_output) + self.assertEqual(custom_hook_output, "Running a thread failed\n") + class TimerTests(BaseTestCase): diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 6ced0470d0..3258298648 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -1041,6 +1041,36 @@ class TestOldPyTime(CPyTimeTestCase, unittest.TestCase): with self.assertRaises(ValueError): pytime_object_to_timespec(float('nan'), time_rnd) +@unittest.skipUnless(sys.platform == "darwin", "test weak linking on macOS") +class TestTimeWeaklinking(unittest.TestCase): + # These test cases verify that weak linking support on macOS works + # as expected. These cases only test new behaviour introduced by weak linking, + # regular behaviour is tested by the normal test cases. + # + # See the section on Weak Linking in Mac/README.txt for more information. + def test_clock_functions(self): + import sysconfig + import platform + + config_vars = sysconfig.get_config_vars() + var_name = "HAVE_CLOCK_GETTIME" + if var_name not in config_vars or not config_vars[var_name]: + raise unittest.SkipTest(f"{var_name} is not available") + + mac_ver = tuple(int(x) for x in platform.mac_ver()[0].split(".")) + + clock_names = [ + "CLOCK_MONOTONIC", "clock_gettime", "clock_gettime_ns", "clock_settime", + "clock_settime_ns", "clock_getres"] + + if mac_ver >= (10, 12): + for name in clock_names: + self.assertTrue(hasattr(time, name), f"time.{name} is not available") + + else: + for name in clock_names: + self.assertFalse(hasattr(time, name), f"time.{name} is available") + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_timeout.py b/Lib/test/test_timeout.py index ac803f5d63..823d5c3e17 100644 --- a/Lib/test/test_timeout.py +++ b/Lib/test/test_timeout.py @@ -122,7 +122,7 @@ class TimeoutTestCase(unittest.TestCase): """ Test the specified socket method. - The method is run at most `count` times and must raise a socket.timeout + The method is run at most `count` times and must raise a TimeoutError within `timeout` + self.fuzz seconds. """ self.sock.settimeout(timeout) @@ -131,11 +131,11 @@ class TimeoutTestCase(unittest.TestCase): t1 = time.monotonic() try: method(*args) - except socket.timeout as e: + except TimeoutError as e: delta = time.monotonic() - t1 break else: - self.fail('socket.timeout was not raised') + self.fail('TimeoutError was not raised') # These checks should account for timing unprecision self.assertLess(delta, timeout + self.fuzz) self.assertGreater(delta, timeout - 1.0) @@ -204,7 +204,7 @@ class TCPTimeoutTestCase(TimeoutTestCase): sock.settimeout(timeout) try: sock.connect((whitehole)) - except socket.timeout: + except TimeoutError: pass except OSError as err: if err.errno == errno.ECONNREFUSED: diff --git a/Lib/test/test_tools/test_i18n.py b/Lib/test/test_tools/test_i18n.py index 8da657907e..12f778dbf8 100644 --- a/Lib/test/test_tools/test_i18n.py +++ b/Lib/test/test_tools/test_i18n.py @@ -220,6 +220,76 @@ class Test_pygettext(unittest.TestCase): ''')) self.assertIn('doc', msgids) + def test_calls_in_fstrings(self): + msgids = self.extract_docstrings_from_str(dedent('''\ + f"{_('foo bar')}" + ''')) + self.assertIn('foo bar', msgids) + + def test_calls_in_fstrings_raw(self): + msgids = self.extract_docstrings_from_str(dedent('''\ + rf"{_('foo bar')}" + ''')) + self.assertIn('foo bar', msgids) + + def test_calls_in_fstrings_nested(self): + msgids = self.extract_docstrings_from_str(dedent('''\ + f"""{f'{_("foo bar")}'}""" + ''')) + self.assertIn('foo bar', msgids) + + def test_calls_in_fstrings_attribute(self): + msgids = self.extract_docstrings_from_str(dedent('''\ + f"{obj._('foo bar')}" + ''')) + self.assertIn('foo bar', msgids) + + def test_calls_in_fstrings_with_call_on_call(self): + msgids = self.extract_docstrings_from_str(dedent('''\ + f"{type(str)('foo bar')}" + ''')) + self.assertNotIn('foo bar', msgids) + + def test_calls_in_fstrings_with_format(self): + msgids = self.extract_docstrings_from_str(dedent('''\ + f"{_('foo {bar}').format(bar='baz')}" + ''')) + self.assertIn('foo {bar}', msgids) + + def test_calls_in_fstrings_with_wrong_input_1(self): + msgids = self.extract_docstrings_from_str(dedent('''\ + f"{_(f'foo {bar}')}" + ''')) + self.assertFalse([msgid for msgid in msgids if 'foo {bar}' in msgid]) + + def test_calls_in_fstrings_with_wrong_input_2(self): + msgids = self.extract_docstrings_from_str(dedent('''\ + f"{_(1)}" + ''')) + self.assertNotIn(1, msgids) + + def test_calls_in_fstring_with_multiple_args(self): + msgids = self.extract_docstrings_from_str(dedent('''\ + f"{_('foo', 'bar')}" + ''')) + self.assertNotIn('foo', msgids) + self.assertNotIn('bar', msgids) + + def test_calls_in_fstring_with_keyword_args(self): + msgids = self.extract_docstrings_from_str(dedent('''\ + f"{_('foo', bar='baz')}" + ''')) + self.assertNotIn('foo', msgids) + self.assertNotIn('bar', msgids) + self.assertNotIn('baz', msgids) + + def test_calls_in_fstring_with_partially_wrong_expression(self): + msgids = self.extract_docstrings_from_str(dedent('''\ + f"{_(f'foo') + _('bar')}" + ''')) + self.assertNotIn('foo', msgids) + self.assertIn('bar', msgids) + def test_files_list(self): """Make sure the directories are inspected for source files bpo-31920 diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 730596efd8..abb5762cd4 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -212,6 +212,26 @@ class TracebackCases(unittest.TestCase): ) self.assertEqual(output.getvalue(), "Exception: projector\n") + def test_print_exception_exc(self): + output = StringIO() + traceback.print_exception(Exception("projector"), file=output) + self.assertEqual(output.getvalue(), "Exception: projector\n") + + def test_format_exception_exc(self): + e = Exception("projector") + output = traceback.format_exception(e) + self.assertEqual(output, ["Exception: projector\n"]) + with self.assertRaisesRegex(ValueError, 'Both or neither'): + traceback.format_exception(e.__class__, e) + with self.assertRaisesRegex(ValueError, 'Both or neither'): + traceback.format_exception(e.__class__, tb=e.__traceback__) + with self.assertRaisesRegex(TypeError, 'positional-only'): + traceback.format_exception(exc=e) + + def test_format_exception_only_exc(self): + output = traceback.format_exception_only(Exception("projector")) + self.assertEqual(output, ["Exception: projector\n"]) + class TracebackFormatTests(unittest.TestCase): @@ -667,6 +687,31 @@ class BaseExceptionReportingTests: msg = self.get_report(e).splitlines() self.assertEqual(msg[-2], ' ^') + def test_syntax_error_no_lineno(self): + # See #34463. + + # Without filename + e = SyntaxError('bad syntax') + msg = self.get_report(e).splitlines() + self.assertEqual(msg, + ['SyntaxError: bad syntax']) + e.lineno = 100 + msg = self.get_report(e).splitlines() + self.assertEqual(msg, + [' File "<string>", line 100', 'SyntaxError: bad syntax']) + + # With filename + e = SyntaxError('bad syntax') + e.filename = 'myfile.py' + + msg = self.get_report(e).splitlines() + self.assertEqual(msg, + ['SyntaxError: bad syntax (myfile.py)']) + e.lineno = 100 + msg = self.get_report(e).splitlines() + self.assertEqual(msg, + [' File "myfile.py", line 100', 'SyntaxError: bad syntax']) + def test_message_none(self): # A message that looks like "None" should not be treated specially err = self.get_report(Exception(None)) @@ -1103,7 +1148,19 @@ class TestTracebackException(unittest.TestCase): self.assertEqual(exc_info[0], exc.exc_type) self.assertEqual(str(exc_info[1]), str(exc)) - def test_comparison(self): + def test_no_refs_to_exception_and_traceback_objects(self): + try: + 1/0 + except Exception: + exc_info = sys.exc_info() + + refcnt1 = sys.getrefcount(exc_info[1]) + refcnt2 = sys.getrefcount(exc_info[2]) + exc = traceback.TracebackException(*exc_info) + self.assertEqual(sys.getrefcount(exc_info[1]), refcnt1) + self.assertEqual(sys.getrefcount(exc_info[2]), refcnt2) + + def test_comparison_basic(self): try: 1/0 except Exception: @@ -1115,6 +1172,53 @@ class TestTracebackException(unittest.TestCase): self.assertNotEqual(exc, object()) self.assertEqual(exc, ALWAYS_EQ) + def test_comparison_params_variations(self): + def raise_exc(): + try: + raise ValueError('bad value') + except: + raise + + def raise_with_locals(): + x, y = 1, 2 + raise_exc() + + try: + raise_with_locals() + except Exception: + exc_info = sys.exc_info() + + exc = traceback.TracebackException(*exc_info) + exc1 = traceback.TracebackException(*exc_info, limit=10) + exc2 = traceback.TracebackException(*exc_info, limit=2) + + self.assertEqual(exc, exc1) # limit=10 gets all frames + self.assertNotEqual(exc, exc2) # limit=2 truncates the output + + # locals change the output + exc3 = traceback.TracebackException(*exc_info, capture_locals=True) + self.assertNotEqual(exc, exc3) + + # there are no locals in the innermost frame + exc4 = traceback.TracebackException(*exc_info, limit=-1) + exc5 = traceback.TracebackException(*exc_info, limit=-1, capture_locals=True) + self.assertEqual(exc4, exc5) + + # there are locals in the next-to-innermost frame + exc6 = traceback.TracebackException(*exc_info, limit=-2) + exc7 = traceback.TracebackException(*exc_info, limit=-2, capture_locals=True) + self.assertNotEqual(exc6, exc7) + + def test_comparison_equivalent_exceptions_are_equal(self): + excs = [] + for _ in range(2): + try: + 1/0 + except: + excs.append(traceback.TracebackException(*sys.exc_info())) + self.assertEqual(excs[0], excs[1]) + self.assertEqual(list(excs[0].format()), list(excs[1].format())) + def test_unhashable(self): class UnhashableException(Exception): def __eq__(self, other): @@ -1156,7 +1260,7 @@ class TestTracebackException(unittest.TestCase): f = test_frame(c, None, None) tb = test_tb(f, 6, None) exc = traceback.TracebackException(Exception, e, tb, lookup_lines=False) - self.assertEqual({}, linecache.cache) + self.assertEqual(linecache.cache, {}) linecache.updatecache('/foo.py', globals()) self.assertEqual(exc.stack[0].line, "import sys") diff --git a/Lib/test/test_tracemalloc.py b/Lib/test/test_tracemalloc.py index a0037f81b8..556656747b 100644 --- a/Lib/test/test_tracemalloc.py +++ b/Lib/test/test_tracemalloc.py @@ -85,6 +85,25 @@ def traceback_filename(filename): return traceback_lineno(filename, 0) +class TestTraceback(unittest.TestCase): + def test_repr(self): + def get_repr(*args) -> str: + return repr(tracemalloc.Traceback(*args)) + + self.assertEqual(get_repr(()), "<Traceback ()>") + self.assertEqual(get_repr((), 0), "<Traceback () total_nframe=0>") + + frames = (("f1", 1), ("f2", 2)) + exp_repr_frames = ( + "(<Frame filename='f2' lineno=2>," + " <Frame filename='f1' lineno=1>)" + ) + self.assertEqual(get_repr(frames), + f"<Traceback {exp_repr_frames}>") + self.assertEqual(get_repr(frames, 2), + f"<Traceback {exp_repr_frames} total_nframe=2>") + + class TestTracemallocEnabled(unittest.TestCase): def setUp(self): if tracemalloc.is_tracing(): @@ -1065,6 +1084,7 @@ class TestCAPI(unittest.TestCase): def test_main(): support.run_unittest( + TestTraceback, TestTracemallocEnabled, TestSnapshot, TestFilters, diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 75c5eee42d..83196ad3c1 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -713,6 +713,30 @@ class TypesTests(unittest.TestCase): assert repr(int | None) == "int | None" assert repr(int | typing.GenericAlias(list, int)) == "int | list[int]" + def test_or_type_operator_with_genericalias(self): + a = list[int] + b = list[str] + c = dict[float, str] + class SubClass(types.GenericAlias): ... + d = SubClass(list, float) + # equivalence with typing.Union + self.assertEqual(a | b | c | d, typing.Union[a, b, c, d]) + # de-duplicate + self.assertEqual(a | c | b | b | a | c | d | d, a | b | c | d) + # order shouldn't matter + self.assertEqual(a | b | d, b | a | d) + self.assertEqual(repr(a | b | c | d), + "list[int] | list[str] | dict[float, str] | list[float]") + + class BadType(type): + def __eq__(self, other): + return 1 / 0 + + bt = BadType('bt', (), {}) + # Comparison should fail and errors should propagate out for bad types. + with self.assertRaises(ZeroDivisionError): + list[int] | list[bt] + def test_ellipsis_type(self): self.assertIsInstance(Ellipsis, types.EllipsisType) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 2ab8be49b2..c340c8a898 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -25,6 +25,7 @@ from typing import IO, TextIO, BinaryIO from typing import Pattern, Match from typing import Annotated, ForwardRef from typing import TypeAlias +from typing import ParamSpec, Concatenate import abc import typing import weakref @@ -447,14 +448,6 @@ class CallableTests(BaseTestCase): def test_callable_wrong_forms(self): with self.assertRaises(TypeError): - Callable[[...], int] - with self.assertRaises(TypeError): - Callable[(), int] - with self.assertRaises(TypeError): - Callable[[()], int] - with self.assertRaises(TypeError): - Callable[[int, 1], 2] - with self.assertRaises(TypeError): Callable[int] def test_callable_instance_works(self): @@ -528,6 +521,7 @@ class LiteralTests(BaseTestCase): self.assertEqual(repr(Literal[int]), "typing.Literal[int]") self.assertEqual(repr(Literal), "typing.Literal") self.assertEqual(repr(Literal[None]), "typing.Literal[None]") + self.assertEqual(repr(Literal[1, 2, 3, 3]), "typing.Literal[1, 2, 3]") def test_cannot_init(self): with self.assertRaises(TypeError): @@ -559,6 +553,35 @@ class LiteralTests(BaseTestCase): with self.assertRaises(TypeError): Literal[1][1] + def test_equal(self): + self.assertNotEqual(Literal[0], Literal[False]) + self.assertNotEqual(Literal[True], Literal[1]) + self.assertNotEqual(Literal[1], Literal[2]) + self.assertNotEqual(Literal[1, True], Literal[1]) + self.assertEqual(Literal[1], Literal[1]) + self.assertEqual(Literal[1, 2], Literal[2, 1]) + self.assertEqual(Literal[1, 2, 3], Literal[1, 2, 3, 3]) + + def test_hash(self): + self.assertEqual(hash(Literal[1]), hash(Literal[1])) + self.assertEqual(hash(Literal[1, 2]), hash(Literal[2, 1])) + self.assertEqual(hash(Literal[1, 2, 3]), hash(Literal[1, 2, 3, 3])) + + def test_args(self): + self.assertEqual(Literal[1, 2, 3].__args__, (1, 2, 3)) + self.assertEqual(Literal[1, 2, 3, 3].__args__, (1, 2, 3)) + self.assertEqual(Literal[1, Literal[2], Literal[3, 4]].__args__, (1, 2, 3, 4)) + # Mutable arguments will not be deduplicated + self.assertEqual(Literal[[], []].__args__, ([], [])) + + def test_flatten(self): + l1 = Literal[Literal[1], Literal[2], Literal[3]] + l2 = Literal[Literal[1, 2], 3] + l3 = Literal[Literal[1, 2, 3]] + for l in l1, l2, l3: + self.assertEqual(l, Literal[1, 2, 3]) + self.assertEqual(l.__args__, (1, 2, 3)) + XK = TypeVar('XK', str, bytes) XV = TypeVar('XV') @@ -1108,10 +1131,6 @@ class ProtocolTests(BaseTestCase): PR[int] with self.assertRaises(TypeError): P[int, str] - with self.assertRaises(TypeError): - PR[int, 1] - with self.assertRaises(TypeError): - PR[int, ClassVar] class C(PR[int, T]): pass @@ -1133,8 +1152,6 @@ class ProtocolTests(BaseTestCase): self.assertIsSubclass(P, PR) with self.assertRaises(TypeError): PR[int] - with self.assertRaises(TypeError): - PR[int, 1] class P1(Protocol, Generic[T]): def bar(self, x: T) -> str: ... @@ -1153,8 +1170,6 @@ class ProtocolTests(BaseTestCase): return x self.assertIsInstance(Test(), PSub) - with self.assertRaises(TypeError): - PR[int, ClassVar] def test_init_called(self): T = TypeVar('T') @@ -1724,8 +1739,6 @@ class GenericTests(BaseTestCase): self.assertEqual(typing.Iterable[Tuple[T, T]][T], typing.Iterable[Tuple[T, T]]) with self.assertRaises(TypeError): Tuple[T, int][()] - with self.assertRaises(TypeError): - Tuple[T, U][T, ...] self.assertEqual(Union[T, int][int], int) self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str]) @@ -1737,10 +1750,6 @@ class GenericTests(BaseTestCase): self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT]) self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]]) - with self.assertRaises(TypeError): - Callable[[T], U][..., int] - with self.assertRaises(TypeError): - Callable[[T], U][[], int] def test_extended_generic_rules_repr(self): T = TypeVar('T') @@ -1777,10 +1786,9 @@ class GenericTests(BaseTestCase): def test_extended_generic_rules_subclassing(self): class T1(Tuple[T, KT]): ... class T2(Tuple[T, ...]): ... - class C1(Callable[[T], T]): ... - class C2(Callable[..., int]): - def __call__(self): - return None + class C1(typing.Container[T]): + def __contains__(self, item): + return False self.assertEqual(T1.__parameters__, (T, KT)) self.assertEqual(T1[int, str].__args__, (int, str)) @@ -1794,10 +1802,9 @@ class GenericTests(BaseTestCase): ## T2[int, str] self.assertEqual(repr(C1[int]).split('.')[-1], 'C1[int]') - self.assertEqual(C2.__parameters__, ()) - self.assertIsInstance(C2(), collections.abc.Callable) - self.assertIsSubclass(C2, collections.abc.Callable) - self.assertIsSubclass(C1, collections.abc.Callable) + self.assertEqual(C1.__parameters__, (T,)) + self.assertIsInstance(C1(), collections.abc.Container) + self.assertIsSubclass(C1, collections.abc.Container) self.assertIsInstance(T1(), tuple) self.assertIsSubclass(T2, tuple) with self.assertRaises(TypeError): @@ -1831,10 +1838,6 @@ class GenericTests(BaseTestCase): class MyTup(Tuple[T, T]): ... self.assertIs(MyTup[int]().__class__, MyTup) self.assertEqual(MyTup[int]().__orig_class__, MyTup[int]) - class MyCall(Callable[..., T]): - def __call__(self): return None - self.assertIs(MyCall[T]().__class__, MyCall) - self.assertEqual(MyCall[T]().__orig_class__, MyCall[T]) class MyDict(typing.Dict[T, T]): ... self.assertIs(MyDict[int]().__class__, MyDict) self.assertEqual(MyDict[int]().__orig_class__, MyDict[int]) @@ -3865,10 +3868,14 @@ class TypedDictTests(BaseTestCase): self.assertEqual(D(), {}) self.assertEqual(D(x=1), {'x': 1}) self.assertEqual(D.__total__, False) + self.assertEqual(D.__required_keys__, frozenset()) + self.assertEqual(D.__optional_keys__, {'x'}) self.assertEqual(Options(), {}) self.assertEqual(Options(log_level=2), {'log_level': 2}) self.assertEqual(Options.__total__, False) + self.assertEqual(Options.__required_keys__, frozenset()) + self.assertEqual(Options.__optional_keys__, {'log_level', 'log_path'}) def test_optional_keys(self): class Point2Dor3D(Point2D, total=False): @@ -4223,6 +4230,111 @@ class TypeAliasTests(BaseTestCase): TypeAlias[int] +class ParamSpecTests(BaseTestCase): + + def test_basic_plain(self): + P = ParamSpec('P') + self.assertEqual(P, P) + self.assertIsInstance(P, ParamSpec) + + def test_valid_uses(self): + P = ParamSpec('P') + T = TypeVar('T') + C1 = Callable[P, int] + self.assertEqual(C1.__args__, (P, int)) + self.assertEqual(C1.__parameters__, (P,)) + C2 = Callable[P, T] + self.assertEqual(C2.__args__, (P, T)) + self.assertEqual(C2.__parameters__, (P, T)) + # Test collections.abc.Callable too. + C3 = collections.abc.Callable[P, int] + self.assertEqual(C3.__args__, (P, int)) + self.assertEqual(C3.__parameters__, (P,)) + C4 = collections.abc.Callable[P, T] + self.assertEqual(C4.__args__, (P, T)) + self.assertEqual(C4.__parameters__, (P, T)) + + # ParamSpec instances should also have args and kwargs attributes. + self.assertIn('args', dir(P)) + self.assertIn('kwargs', dir(P)) + P.args + P.kwargs + + def test_user_generics(self): + T = TypeVar("T") + P = ParamSpec("P") + P_2 = ParamSpec("P_2") + + class X(Generic[T, P]): + f: Callable[P, int] + x: T + G1 = X[int, P_2] + self.assertEqual(G1.__args__, (int, P_2)) + self.assertEqual(G1.__parameters__, (P_2,)) + + G2 = X[int, Concatenate[int, P_2]] + self.assertEqual(G2.__args__, (int, Concatenate[int, P_2])) + self.assertEqual(G2.__parameters__, (P_2,)) + + G3 = X[int, [int, bool]] + self.assertEqual(G3.__args__, (int, (int, bool))) + self.assertEqual(G3.__parameters__, ()) + + G4 = X[int, ...] + self.assertEqual(G4.__args__, (int, Ellipsis)) + self.assertEqual(G4.__parameters__, ()) + + class Z(Generic[P]): + f: Callable[P, int] + + G5 = Z[[int, str, bool]] + self.assertEqual(G5.__args__, ((int, str, bool),)) + self.assertEqual(G5.__parameters__, ()) + + G6 = Z[int, str, bool] + self.assertEqual(G6.__args__, ((int, str, bool),)) + self.assertEqual(G6.__parameters__, ()) + + # G5 and G6 should be equivalent according to the PEP + self.assertEqual(G5.__args__, G6.__args__) + self.assertEqual(G5.__origin__, G6.__origin__) + self.assertEqual(G5.__parameters__, G6.__parameters__) + self.assertEqual(G5, G6) + + def test_var_substitution(self): + T = TypeVar("T") + P = ParamSpec("P") + C1 = Callable[P, T] + self.assertEqual(C1[int, str], Callable[[int], str]) + self.assertEqual(C1[[int, str, dict], float], Callable[[int, str, dict], float]) + + +class ConcatenateTests(BaseTestCase): + def test_basics(self): + P = ParamSpec('P') + class MyClass: ... + c = Concatenate[MyClass, P] + self.assertNotEqual(c, Concatenate) + + def test_valid_uses(self): + P = ParamSpec('P') + T = TypeVar('T') + C1 = Callable[Concatenate[int, P], int] + self.assertEqual(C1.__args__, (Concatenate[int, P], int)) + self.assertEqual(C1.__parameters__, (P,)) + C2 = Callable[Concatenate[int, T, P], T] + self.assertEqual(C2.__args__, (Concatenate[int, T, P], T)) + self.assertEqual(C2.__parameters__, (T, P)) + + # Test collections.abc.Callable too. + C3 = collections.abc.Callable[Concatenate[int, P], int] + self.assertEqual(C3.__args__, (Concatenate[int, P], int)) + self.assertEqual(C3.__parameters__, (P,)) + C4 = collections.abc.Callable[Concatenate[int, T, P], T] + self.assertEqual(C4.__args__, (Concatenate[int, T, P], T)) + self.assertEqual(C4.__parameters__, (T, P)) + + class AllTests(BaseTestCase): """Tests for __all__.""" diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py index 90b0965582..4f5636e142 100644 --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_unicode.py @@ -2516,11 +2516,13 @@ class CAPITest(unittest.TestCase): def test_from_format(self): import_helper.import_module('ctypes') from ctypes import ( + c_char_p, pythonapi, py_object, sizeof, c_int, c_long, c_longlong, c_ssize_t, c_uint, c_ulong, c_ulonglong, c_size_t, c_void_p) name = "PyUnicode_FromFormat" _PyUnicode_FromFormat = getattr(pythonapi, name) + _PyUnicode_FromFormat.argtypes = (c_char_p,) _PyUnicode_FromFormat.restype = py_object def PyUnicode_FromFormat(format, *args): diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index 532aa3a639..c7c8613ea2 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -152,6 +152,18 @@ class UnparseTestCase(ASTTestCase): # See issue 25180 self.check_ast_roundtrip(r"""f'{f"{0}"*3}'""") self.check_ast_roundtrip(r"""f'{f"{y}"*3}'""") + self.check_ast_roundtrip("""f''""") + self.check_ast_roundtrip('''f"""'end' "quote\\""""''') + + def test_fstrings_complicated(self): + # See issue 28002 + self.check_ast_roundtrip("""f'''{"'"}'''""") + self.check_ast_roundtrip('''f\'\'\'-{f"""*{f"+{f'.{x}.'}+"}*"""}-\'\'\'''') + self.check_ast_roundtrip('''f\'\'\'-{f"""*{f"+{f'.{x}.'}+"}*"""}-'single quote\\'\'\'\'''') + self.check_ast_roundtrip('f"""{\'\'\'\n\'\'\'}"""') + self.check_ast_roundtrip('f"""{g(\'\'\'\n\'\'\')}"""') + self.check_ast_roundtrip('''f"a\\r\\nb"''') + self.check_ast_roundtrip('''f"\\u2028{'x'}"''') def test_strings(self): self.check_ast_roundtrip("u'foo'") @@ -311,6 +323,9 @@ class UnparseTestCase(ASTTestCase): ) ) + def test_invalid_fstring_backslash(self): + self.check_invalid(ast.FormattedValue(value=ast.Constant(value="\\\\"))) + def test_invalid_set(self): self.check_invalid(ast.Set(elts=[])) @@ -330,8 +345,8 @@ class UnparseTestCase(ASTTestCase): '\r\\r\t\\t\n\\n', '""">>> content = \"\"\"blabla\"\"\" <<<"""', r'foo\n\x00', - 'ðŸâ›Žð©¸½Ã¼Ã©ÅŸ^\N{LONG RIGHTWARDS SQUIGGLE ARROW}' - + "' \\'\\'\\'\"\"\" \"\"\\'\\' \\'", + 'ðŸâ›Žð©¸½Ã¼Ã©ÅŸ^\\\\X\\\\BB\N{LONG RIGHTWARDS SQUIGGLE ARROW}' ) for docstring in docstrings: # check as Module docstrings for easy testing @@ -416,7 +431,6 @@ class CosmeticTestCase(ASTTestCase): self.check_src_roundtrip("call((yield x))") self.check_src_roundtrip("return x + (yield x)") - def test_class_bases_and_keywords(self): self.check_src_roundtrip("class X:\n pass") self.check_src_roundtrip("class X(A):\n pass") @@ -429,6 +443,13 @@ class CosmeticTestCase(ASTTestCase): self.check_src_roundtrip("class X(*args):\n pass") self.check_src_roundtrip("class X(*args, **kwargs):\n pass") + def test_fstrings(self): + self.check_src_roundtrip('''f\'\'\'-{f"""*{f"+{f'.{x}.'}+"}*"""}-\'\'\'''') + self.check_src_roundtrip('''f"\\u2028{'x'}"''') + self.check_src_roundtrip(r"f'{x}\n'") + self.check_src_roundtrip('''f''\'{"""\n"""}\\n''\'''') + self.check_src_roundtrip('''f''\'{f"""{x}\n"""}\\n''\'''') + def test_docstrings(self): docstrings = ( '"""simple doc string"""', @@ -443,6 +464,10 @@ class CosmeticTestCase(ASTTestCase): '""""""', '"""\'\'\'"""', '"""\'\'\'\'\'\'"""', + '"""ðŸâ›Žð©¸½Ã¼Ã©ÅŸ^\\\\X\\\\BB⟿"""', + '"""end in single \'quote\'"""', + "'''end in double \"quote\"'''", + '"""almost end in double "quote"."""', ) for prefix in docstring_prefixes: @@ -483,9 +508,8 @@ class DirectoryTestCase(ASTTestCase): lib_dir = pathlib.Path(__file__).parent / ".." test_directories = (lib_dir, lib_dir / "test") - skip_files = {"test_fstring.py"} run_always_files = {"test_grammar.py", "test_syntax.py", "test_compile.py", - "test_ast.py", "test_asdl_parser.py"} + "test_ast.py", "test_asdl_parser.py", "test_fstring.py"} _files_to_test = None @@ -525,14 +549,6 @@ class DirectoryTestCase(ASTTestCase): if test.support.verbose: print(f"Testing {item.absolute()}") - # Some f-strings are not correctly round-tripped by - # Tools/parser/unparse.py. See issue 28002 for details. - # We need to skip files that contain such f-strings. - if item.name in self.skip_files: - if test.support.verbose: - print(f"Skipping {item.absolute()}: see issue 28002") - continue - with self.subTest(filename=item): source = read_pyfile(item) self.check_ast_roundtrip(source) diff --git a/Lib/test/test_urllib2net.py b/Lib/test/test_urllib2net.py index c1d55ee8b2..4750ad9600 100644 --- a/Lib/test/test_urllib2net.py +++ b/Lib/test/test_urllib2net.py @@ -277,7 +277,7 @@ class OtherNetworkTests(unittest.TestCase): ioerror_peer_reset: buf = f.read() debug("read %d bytes" % len(buf)) - except socket.timeout: + except TimeoutError: print("<timeout: %s>" % url, file=sys.stderr) f.close() time.sleep(0.1) diff --git a/Lib/test/test_utf8_mode.py b/Lib/test/test_utf8_mode.py index bdb93457cf..8b6332ee22 100644 --- a/Lib/test/test_utf8_mode.py +++ b/Lib/test/test_utf8_mode.py @@ -3,11 +3,13 @@ Test the implementation of the PEP 540: the UTF-8 Mode. """ import locale +import subprocess import sys import textwrap import unittest from test import support from test.support.script_helper import assert_python_ok, assert_python_failure +from test.support import os_helper MS_WINDOWS = (sys.platform == 'win32') @@ -250,6 +252,31 @@ class UTF8ModeTests(unittest.TestCase): out = self.get_output('-X', 'utf8', '-E', '-c', code) self.assertEqual(out, '1') + @unittest.skipIf(MS_WINDOWS, + "os.device_encoding() doesn't implement " + "the UTF-8 Mode on Windows") + def test_device_encoding(self): + # Use stdout as TTY + if not sys.stdout.isatty(): + self.skipTest("sys.stdout is not a TTY") + + filename = 'out.txt' + self.addCleanup(os_helper.unlink, filename) + + code = (f'import os, sys; fd = sys.stdout.fileno(); ' + f'out = open({filename!r}, "w", encoding="utf-8"); ' + f'print(os.isatty(fd), os.device_encoding(fd), file=out); ' + f'out.close()') + cmd = [sys.executable, '-X', 'utf8', '-c', code] + # The stdout TTY is inherited to the child process + proc = subprocess.run(cmd, text=True) + self.assertEqual(proc.returncode, 0, proc) + + # In UTF-8 Mode, device_encoding(fd) returns "UTF-8" if fd is a TTY + with open(filename, encoding="utf8") as fp: + out = fp.read().rstrip() + self.assertEqual(out, 'True UTF-8') + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 718113d6e1..d6a8333427 100644..100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -639,7 +639,7 @@ class BaseTestUUID: equal(u, self.uuid.UUID(v)) equal(str(u), v) - @unittest.skipUnless(os.name == 'posix', 'requires Posix') + @unittest.skipUnless(hasattr(os, 'fork'), 'need os.fork') def testIssue8621(self): # On at least some versions of OSX self.uuid.uuid4 generates # the same sequence of UUIDs in the parent and any diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 5bb62cdb37..098ba17af5 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -446,7 +446,7 @@ class EnsurePipTest(BaseTest): # pip's cross-version compatibility may trigger deprecation # warnings in current versions of Python. Ensure related # environment settings don't cause venv to fail. - envvars["PYTHONWARNINGS"] = "e" + envvars["PYTHONWARNINGS"] = "ignore" # ensurepip is different enough from a normal pip invocation # that we want to ensure it ignores the normal pip environment # variable settings. We set PIP_NO_INSTALL here specifically @@ -485,7 +485,8 @@ class EnsurePipTest(BaseTest): # Ensure pip is available in the virtual environment envpy = os.path.join(os.path.realpath(self.env_dir), self.bindir, self.exe) # Ignore DeprecationWarning since pip code is not part of Python - out, err = check_output([envpy, '-W', 'ignore::DeprecationWarning', '-I', + out, err = check_output([envpy, '-W', 'ignore::DeprecationWarning', + '-W', 'ignore::ImportWarning', '-I', '-m', 'pip', '--version']) # We force everything to text, so unittest gives the detailed diff # if we get unexpected results @@ -501,8 +502,12 @@ class EnsurePipTest(BaseTest): # Check the private uninstall command provided for the Windows # installers works (at least in a virtual environment) with EnvironmentVarGuard() as envvars: + # It seems ensurepip._uninstall calls subprocesses which do not + # inherit the interpreter settings. + envvars["PYTHONWARNINGS"] = "ignore" out, err = check_output([envpy, - '-W', 'ignore::DeprecationWarning', '-I', + '-W', 'ignore::DeprecationWarning', + '-W', 'ignore::ImportWarning', '-I', '-m', 'ensurepip._uninstall']) # We force everything to text, so unittest gives the detailed diff # if we get unexpected results diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 3f1f3781e4..fd4a38527f 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -2852,8 +2852,12 @@ class ElementFindTest(unittest.TestCase): ['tag'] * 3) self.assertEqual(summarize_list(e.findall('.//tag[@class="a"]')), ['tag']) + self.assertEqual(summarize_list(e.findall('.//tag[@class!="a"]')), + ['tag'] * 2) self.assertEqual(summarize_list(e.findall('.//tag[@class="b"]')), ['tag'] * 2) + self.assertEqual(summarize_list(e.findall('.//tag[@class!="b"]')), + ['tag']) self.assertEqual(summarize_list(e.findall('.//tag[@id]')), ['tag']) self.assertEqual(summarize_list(e.findall('.//section[tag]')), @@ -2875,6 +2879,19 @@ class ElementFindTest(unittest.TestCase): self.assertEqual(summarize_list(e.findall(".//section[ tag = 'subtext' ]")), ['section']) + # Negations of above tests. They match nothing because the sole section + # tag has subtext. + self.assertEqual(summarize_list(e.findall(".//section[tag!='subtext']")), + []) + self.assertEqual(summarize_list(e.findall(".//section[tag !='subtext']")), + []) + self.assertEqual(summarize_list(e.findall(".//section[tag!= 'subtext']")), + []) + self.assertEqual(summarize_list(e.findall(".//section[tag != 'subtext']")), + []) + self.assertEqual(summarize_list(e.findall(".//section[ tag != 'subtext' ]")), + []) + self.assertEqual(summarize_list(e.findall(".//tag[.='subtext']")), ['tag']) self.assertEqual(summarize_list(e.findall(".//tag[. ='subtext']")), @@ -2890,6 +2907,24 @@ class ElementFindTest(unittest.TestCase): self.assertEqual(summarize_list(e.findall(".//tag[.= ' subtext']")), []) + # Negations of above tests. + # Matches everything but the tag containing subtext + self.assertEqual(summarize_list(e.findall(".//tag[.!='subtext']")), + ['tag'] * 3) + self.assertEqual(summarize_list(e.findall(".//tag[. !='subtext']")), + ['tag'] * 3) + self.assertEqual(summarize_list(e.findall('.//tag[.!= "subtext"]')), + ['tag'] * 3) + self.assertEqual(summarize_list(e.findall('.//tag[ . != "subtext" ]')), + ['tag'] * 3) + self.assertEqual(summarize_list(e.findall(".//tag[. != 'subtext']")), + ['tag'] * 3) + # Matches all tags. + self.assertEqual(summarize_list(e.findall(".//tag[. != 'subtext ']")), + ['tag'] * 4) + self.assertEqual(summarize_list(e.findall(".//tag[.!= ' subtext']")), + ['tag'] * 4) + # duplicate section => 2x tag matches e[1] = e[2] self.assertEqual(summarize_list(e.findall(".//section[tag = 'subtext']")), diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py index 3dfa84bf77..c54aeb1094 100644 --- a/Lib/test/test_xmlrpc.py +++ b/Lib/test/test_xmlrpc.py @@ -648,7 +648,7 @@ def http_server(evt, numrequests, requestHandler=None, encoding=None): serv.handle_request() numrequests -= 1 - except socket.timeout: + except TimeoutError: pass finally: serv.socket.close() @@ -713,7 +713,7 @@ def http_multi_server(evt, numrequests, requestHandler=None): serv.handle_request() numrequests -= 1 - except socket.timeout: + except TimeoutError: pass finally: serv.socket.close() diff --git a/Lib/test/test_xxlimited.py b/Lib/test/test_xxlimited.py new file mode 100644 index 0000000000..e3f521d9b0 --- /dev/null +++ b/Lib/test/test_xxlimited.py @@ -0,0 +1,79 @@ +import unittest +from test.support import import_helper +import types + +xxlimited = import_helper.import_module('xxlimited') +xxlimited_35 = import_helper.import_module('xxlimited_35') + + +class CommonTests: + module: types.ModuleType + + def test_xxo_new(self): + xxo = self.module.Xxo() + + def test_xxo_attributes(self): + xxo = self.module.Xxo() + with self.assertRaises(AttributeError): + xxo.foo + with self.assertRaises(AttributeError): + del xxo.foo + + xxo.foo = 1234 + self.assertEqual(xxo.foo, 1234) + + del xxo.foo + with self.assertRaises(AttributeError): + xxo.foo + + def test_foo(self): + # the foo function adds 2 numbers + self.assertEqual(self.module.foo(1, 2), 3) + + def test_str(self): + self.assertTrue(issubclass(self.module.Str, str)) + self.assertIsNot(self.module.Str, str) + + custom_string = self.module.Str("abcd") + self.assertEqual(custom_string, "abcd") + self.assertEqual(custom_string.upper(), "ABCD") + + def test_new(self): + xxo = self.module.new() + self.assertEqual(xxo.demo("abc"), "abc") + + +class TestXXLimited(CommonTests, unittest.TestCase): + module = xxlimited + + def test_xxo_demo(self): + xxo = self.module.Xxo() + other = self.module.Xxo() + self.assertEqual(xxo.demo("abc"), "abc") + self.assertEqual(xxo.demo(xxo), xxo) + self.assertEqual(xxo.demo(other), other) + self.assertEqual(xxo.demo(0), None) + + def test_error(self): + with self.assertRaises(self.module.Error): + raise self.module.Error + + +class TestXXLimited35(CommonTests, unittest.TestCase): + module = xxlimited_35 + + def test_xxo_demo(self): + xxo = self.module.Xxo() + other = self.module.Xxo() + self.assertEqual(xxo.demo("abc"), "abc") + self.assertEqual(xxo.demo(0), None) + + def test_roj(self): + # the roj function always fails + with self.assertRaises(SystemError): + self.module.roj(0) + + def test_null(self): + null1 = self.module.Null() + null2 = self.module.Null() + self.assertNotEqual(null1, null2) diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py index b3c24213f3..7c09e2f51b 100644 --- a/Lib/test/test_zipfile.py +++ b/Lib/test/test_zipfile.py @@ -2966,6 +2966,12 @@ class TestPath(unittest.TestCase): assert e.read_text() == "content of e" @pass_alpharep + def test_joinpath_multiple(self, alpharep): + root = zipfile.Path(alpharep) + e = root.joinpath("b", "d", "e.txt") + assert e.read_text() == "content of e" + + @pass_alpharep def test_traverse_truediv(self, alpharep): root = zipfile.Path(alpharep) a = root / "a.txt" diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py index 8df7489f75..d59ef1ed6f 100644 --- a/Lib/test/test_zipimport.py +++ b/Lib/test/test_zipimport.py @@ -7,6 +7,7 @@ import struct import time import unittest import unittest.mock +import warnings from test import support from test.support import import_helper @@ -241,10 +242,10 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase): files = {TESTMOD + pyc_ext: (NOW, badmagic_pyc)} try: self.doTest(".py", files, TESTMOD) - except ImportError: - pass - else: - self.fail("expected ImportError; import from bad pyc") + self.fail("This should not be reached") + except zipimport.ZipImportError as exc: + self.assertIsInstance(exc.__cause__, ImportError) + self.assertIn("magic number", exc.__cause__.msg) def testBadMTime(self): badtime_pyc = bytearray(test_pyc) @@ -450,37 +451,54 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase): zi = zipimport.zipimporter(TEMP_ZIP) self.assertEqual(zi.archive, TEMP_ZIP) - self.assertEqual(zi.is_package(TESTPACK), True) - - find_mod = zi.find_module('spam') - self.assertIsNotNone(find_mod) - self.assertIsInstance(find_mod, zipimport.zipimporter) - self.assertFalse(find_mod.is_package('spam')) - load_mod = find_mod.load_module('spam') - self.assertEqual(find_mod.get_filename('spam'), load_mod.__file__) - - mod = zi.load_module(TESTPACK) + self.assertTrue(zi.is_package(TESTPACK)) + + # PEP 302 + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + find_mod = zi.find_module('spam') + self.assertIsNotNone(find_mod) + self.assertIsInstance(find_mod, zipimport.zipimporter) + self.assertFalse(find_mod.is_package('spam')) + load_mod = find_mod.load_module('spam') + self.assertEqual(find_mod.get_filename('spam'), load_mod.__file__) + + mod = zi.load_module(TESTPACK) + self.assertEqual(zi.get_filename(TESTPACK), mod.__file__) + + # PEP 451 + spec = zi.find_spec('spam') + self.assertIsNotNone(spec) + self.assertIsInstance(spec.loader, zipimport.zipimporter) + self.assertFalse(spec.loader.is_package('spam')) + exec_mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(exec_mod) + self.assertEqual(spec.loader.get_filename('spam'), exec_mod.__file__) + + spec = zi.find_spec(TESTPACK) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) self.assertEqual(zi.get_filename(TESTPACK), mod.__file__) existing_pack_path = importlib.import_module(TESTPACK).__path__[0] expected_path_path = os.path.join(TEMP_ZIP, TESTPACK) self.assertEqual(existing_pack_path, expected_path_path) - self.assertEqual(zi.is_package(packdir + '__init__'), False) - self.assertEqual(zi.is_package(packdir + TESTPACK2), True) - self.assertEqual(zi.is_package(packdir2 + TESTMOD), False) + self.assertFalse(zi.is_package(packdir + '__init__')) + self.assertTrue(zi.is_package(packdir + TESTPACK2)) + self.assertFalse(zi.is_package(packdir2 + TESTMOD)) mod_path = packdir2 + TESTMOD mod_name = module_path_to_dotted_name(mod_path) mod = importlib.import_module(mod_name) self.assertTrue(mod_name in sys.modules) - self.assertEqual(zi.get_source(TESTPACK), None) - self.assertEqual(zi.get_source(mod_path), None) + self.assertIsNone(zi.get_source(TESTPACK)) + self.assertIsNone(zi.get_source(mod_path)) self.assertEqual(zi.get_filename(mod_path), mod.__file__) # To pass in the module name instead of the path, we must use the # right importer - loader = mod.__loader__ - self.assertEqual(loader.get_source(mod_name), None) + loader = mod.__spec__.loader + self.assertIsNone(loader.get_source(mod_name)) self.assertEqual(loader.get_filename(mod_name), mod.__file__) # test prefix and archivepath members @@ -505,36 +523,55 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase): zi = zipimport.zipimporter(TEMP_ZIP + os.sep + packdir) self.assertEqual(zi.archive, TEMP_ZIP) self.assertEqual(zi.prefix, packdir) - self.assertEqual(zi.is_package(TESTPACK2), True) - mod = zi.load_module(TESTPACK2) - self.assertEqual(zi.get_filename(TESTPACK2), mod.__file__) - - self.assertEqual( - zi.is_package(TESTPACK2 + os.sep + '__init__'), False) - self.assertEqual( - zi.is_package(TESTPACK2 + os.sep + TESTMOD), False) + self.assertTrue(zi.is_package(TESTPACK2)) + # PEP 302 + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + mod = zi.load_module(TESTPACK2) + self.assertEqual(zi.get_filename(TESTPACK2), mod.__file__) + # PEP 451 + spec = zi.find_spec(TESTPACK2) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + self.assertEqual(spec.loader.get_filename(TESTPACK2), mod.__file__) + + self.assertFalse(zi.is_package(TESTPACK2 + os.sep + '__init__')) + self.assertFalse(zi.is_package(TESTPACK2 + os.sep + TESTMOD)) pkg_path = TEMP_ZIP + os.sep + packdir + TESTPACK2 zi2 = zipimport.zipimporter(pkg_path) - find_mod_dotted = zi2.find_module(TESTMOD) - self.assertIsNotNone(find_mod_dotted) - self.assertIsInstance(find_mod_dotted, zipimport.zipimporter) - self.assertFalse(zi2.is_package(TESTMOD)) - load_mod = find_mod_dotted.load_module(TESTMOD) + # PEP 302 + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + find_mod_dotted = zi2.find_module(TESTMOD) + self.assertIsNotNone(find_mod_dotted) + self.assertIsInstance(find_mod_dotted, zipimport.zipimporter) + self.assertFalse(zi2.is_package(TESTMOD)) + load_mod = find_mod_dotted.load_module(TESTMOD) + self.assertEqual( + find_mod_dotted.get_filename(TESTMOD), load_mod.__file__) + + # PEP 451 + spec = zi2.find_spec(TESTMOD) + self.assertIsNotNone(spec) + self.assertIsInstance(spec.loader, zipimport.zipimporter) + self.assertFalse(spec.loader.is_package(TESTMOD)) + load_mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(load_mod) self.assertEqual( - find_mod_dotted.get_filename(TESTMOD), load_mod.__file__) + spec.loader.get_filename(TESTMOD), load_mod.__file__) mod_path = TESTPACK2 + os.sep + TESTMOD mod_name = module_path_to_dotted_name(mod_path) mod = importlib.import_module(mod_name) self.assertTrue(mod_name in sys.modules) - self.assertEqual(zi.get_source(TESTPACK2), None) - self.assertEqual(zi.get_source(mod_path), None) + self.assertIsNone(zi.get_source(TESTPACK2)) + self.assertIsNone(zi.get_source(mod_path)) self.assertEqual(zi.get_filename(mod_path), mod.__file__) # To pass in the module name instead of the path, we must use the # right importer. loader = mod.__loader__ - self.assertEqual(loader.get_source(mod_name), None) + self.assertIsNone(loader.get_source(mod_name)) self.assertEqual(loader.get_filename(mod_name), mod.__file__) def testGetData(self): @@ -655,7 +692,9 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase): zinfo = ZipInfo(TESTMOD + ".py", time.localtime(NOW)) zinfo.compress_type = self.compression z.writestr(zinfo, test_src) - zipimport.zipimporter(filename).load_module(TESTMOD) + spec = zipimport.zipimporter(filename).find_spec(TESTMOD) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) def testBytesPath(self): filename = os_helper.TESTFN + ".zip" @@ -746,17 +785,24 @@ class BadFileZipImportTestCase(unittest.TestCase): z = zipimport.zipimporter(TESTMOD) try: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + self.assertRaises(TypeError, z.load_module, None) self.assertRaises(TypeError, z.find_module, None) - self.assertRaises(TypeError, z.load_module, None) + self.assertRaises(TypeError, z.find_spec, None) + self.assertRaises(TypeError, z.exec_module, None) self.assertRaises(TypeError, z.is_package, None) self.assertRaises(TypeError, z.get_code, None) self.assertRaises(TypeError, z.get_data, None) self.assertRaises(TypeError, z.get_source, None) error = zipimport.ZipImportError - self.assertEqual(z.find_module('abc'), None) + self.assertIsNone(z.find_module('abc')) + self.assertIsNone(z.find_spec('abc')) - self.assertRaises(error, z.load_module, 'abc') + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + self.assertRaises(error, z.load_module, 'abc') self.assertRaises(error, z.get_code, 'abc') self.assertRaises(OSError, z.get_data, 'abc') self.assertRaises(error, z.get_source, 'abc') diff --git a/Lib/threading.py b/Lib/threading.py index 06c77f70fe..7b3d63dd21 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -28,7 +28,7 @@ __all__ = ['get_ident', 'active_count', 'Condition', 'current_thread', 'Event', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread', 'Barrier', 'BrokenBarrierError', 'Timer', 'ThreadError', 'setprofile', 'settrace', 'local', 'stack_size', - 'excepthook', 'ExceptHookArgs'] + 'excepthook', 'ExceptHookArgs', 'gettrace', 'getprofile'] # Rename some stuff so "from threading import *" is safe _start_new_thread = _thread.start_new_thread @@ -65,6 +65,10 @@ def setprofile(func): global _profile_hook _profile_hook = func +def getprofile(): + """Get the profiler function as set by threading.setprofile().""" + return _profile_hook + def settrace(func): """Set a trace function for all threads started from the threading module. @@ -75,6 +79,10 @@ def settrace(func): global _trace_hook _trace_hook = func +def gettrace(): + """Get the trace function as set by threading.settrace().""" + return _trace_hook + # Synchronization classes Lock = _allocate_lock @@ -836,8 +844,12 @@ class Thread: # they may be in an invalid state leading to a deadlock or crash. self._started._at_fork_reinit() if is_alive: - self._tstate_lock._at_fork_reinit() - self._tstate_lock.acquire() + # bpo-42350: If the fork happens when the thread is already stopped + # (ex: after threading._shutdown() has been called), _tstate_lock + # is None. Do nothing in this case. + if self._tstate_lock is not None: + self._tstate_lock._at_fork_reinit() + self._tstate_lock.acquire() else: # The thread isn't alive after fork: it doesn't have a tstate # anymore. @@ -1192,6 +1204,10 @@ except ImportError: stderr.flush() +# Original value of threading.excepthook +__excepthook__ = excepthook + + def _make_invoke_excepthook(): # Create a local namespace to ensure that variables remain alive # when _invoke_excepthook() is called, even if it is called late during diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 3bfeb7a017..fec56553e4 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -270,7 +270,7 @@ class Event: ) -_support_default_root = 1 +_support_default_root = True _default_root = None @@ -280,13 +280,51 @@ def NoDefaultRoot(): Call this function to inhibit that the first instance of Tk is used for windows without an explicit parent window. """ - global _support_default_root - _support_default_root = 0 - global _default_root + global _support_default_root, _default_root + _support_default_root = False + # Delete, so any use of _default_root will immediately raise an exception. + # Rebind before deletion, so repeated calls will not fail. _default_root = None del _default_root +def _get_default_root(what=None): + if not _support_default_root: + raise RuntimeError("No master specified and tkinter is " + "configured to not support default root") + if _default_root is None: + if what: + raise RuntimeError(f"Too early to {what}: no default root window") + root = Tk() + assert _default_root is root + return _default_root + + +def _get_temp_root(): + global _support_default_root + if not _support_default_root: + raise RuntimeError("No master specified and tkinter is " + "configured to not support default root") + root = _default_root + if root is None: + assert _support_default_root + _support_default_root = False + root = Tk() + _support_default_root = True + assert _default_root is None + root.withdraw() + root._temporary = True + return root + + +def _destroy_temp_root(master): + if getattr(master, '_temporary', False): + try: + master.destroy() + except TclError: + pass + + def _tkerror(err): """Internal function.""" pass @@ -329,8 +367,8 @@ class Variable: if name is not None and not isinstance(name, str): raise TypeError("name must be a string") global _varnum - if not master: - master = _default_root + if master is None: + master = _get_default_root('create variable') self._root = master._root() self._tk = master.tk if name: @@ -591,7 +629,7 @@ class BooleanVar(Variable): def mainloop(n=0): """Run the main loop of Tcl.""" - _default_root.tk.mainloop(n) + _get_default_root('run the main loop').tk.mainloop(n) getint = int @@ -600,9 +638,9 @@ getdouble = float def getboolean(s): - """Convert true and false to integer values 1 and 0.""" + """Convert Tcl object to True or False.""" try: - return _default_root.tk.getboolean(s) + return _get_default_root('use getboolean()').tk.getboolean(s) except TclError: raise ValueError("invalid literal for getboolean()") @@ -795,7 +833,7 @@ class Misc: function which shall be called. Additional parameters are given as parameters to the function call. Return identifier to cancel scheduling with after_cancel.""" - if not func: + if func is None: # I'd rather use time.sleep(ms*0.001) self.tk.call('after', ms) return None @@ -1529,7 +1567,7 @@ class Misc: def _root(self): """Internal function.""" w = self - while w.master: w = w.master + while w.master is not None: w = w.master return w _subst_format = ('%#', '%b', '%f', '%h', '%k', '%s', '%t', '%w', '%x', '%y', @@ -2248,7 +2286,7 @@ class Tk(Misc, Wm): is the name of the widget class.""" self.master = None self.children = {} - self._tkloaded = 0 + self._tkloaded = False # to avoid recursions in the getattr code in case of failure, we # ensure that self.tk is always _something_. self.tk = None @@ -2272,7 +2310,7 @@ class Tk(Misc, Wm): self._loadtk() def _loadtk(self): - self._tkloaded = 1 + self._tkloaded = True global _default_root # Version sanity checks tk_version = self.tk.getvar('tk_version') @@ -2293,7 +2331,7 @@ class Tk(Misc, Wm): self.tk.createcommand('exit', _exit) self._tclCommands.append('tkerror') self._tclCommands.append('exit') - if _support_default_root and not _default_root: + if _support_default_root and _default_root is None: _default_root = self self.protocol("WM_DELETE_WINDOW", self.destroy) @@ -2521,12 +2559,8 @@ class BaseWidget(Misc): def _setup(self, master, cnf): """Internal function. Sets up information about children.""" - if _support_default_root: - global _default_root - if not master: - if not _default_root: - _default_root = Tk() - master = _default_root + if master is None: + master = _get_default_root() self.master = master self.tk = master.tk name = None @@ -3940,7 +3974,7 @@ class _setit: def __call__(self, *args): self.__var.set(self.__value) - if self.__callback: + if self.__callback is not None: self.__callback(self.__value, *args) @@ -3989,10 +4023,8 @@ class Image: def __init__(self, imgtype, name=None, cnf={}, master=None, **kw): self.name = None - if not master: - master = _default_root - if not master: - raise RuntimeError('Too early to create image') + if master is None: + master = _get_default_root('create image') self.tk = getattr(master, 'tk', master) if not name: Image._last_id += 1 @@ -4146,11 +4178,13 @@ class BitmapImage(Image): def image_names(): - return _default_root.tk.splitlist(_default_root.tk.call('image', 'names')) + tk = _get_default_root('use image_names()').tk + return tk.splitlist(tk.call('image', 'names')) def image_types(): - return _default_root.tk.splitlist(_default_root.tk.call('image', 'types')) + tk = _get_default_root('use image_types()').tk + return tk.splitlist(tk.call('image', 'types')) class Spinbox(Widget, XView): diff --git a/Lib/tkinter/commondialog.py b/Lib/tkinter/commondialog.py index e56b5baf7d..e595c99def 100644 --- a/Lib/tkinter/commondialog.py +++ b/Lib/tkinter/commondialog.py @@ -10,7 +10,7 @@ __all__ = ["Dialog"] -from tkinter import Frame +from tkinter import Frame, _get_temp_root, _destroy_temp_root class Dialog: @@ -18,10 +18,10 @@ class Dialog: command = None def __init__(self, master=None, **options): + if master is None: + master = options.get('parent') self.master = master self.options = options - if not master and options.get('parent'): - self.master = options['parent'] def _fixoptions(self): pass # hook @@ -37,22 +37,17 @@ class Dialog: self._fixoptions() - # we need a dummy widget to properly process the options - # (at least as long as we use Tkinter 1.63) - w = Frame(self.master) - + master = self.master + if master is None: + master = _get_temp_root() try: - - s = w.tk.call(self.command, *w._options(self.options)) - - s = self._fixresult(w, s) - + self._test_callback(master) # The function below is replaced for some tests. + s = master.tk.call(self.command, *master._options(self.options)) + s = self._fixresult(master, s) finally: - - try: - # get rid of the widget - w.destroy() - except: - pass + _destroy_temp_root(master) return s + + def _test_callback(self, master): + pass diff --git a/Lib/tkinter/dnd.py b/Lib/tkinter/dnd.py index 3120ff342f..acec61ba71 100644 --- a/Lib/tkinter/dnd.py +++ b/Lib/tkinter/dnd.py @@ -108,7 +108,7 @@ __all__ = ["dnd_start", "DndHandler"] def dnd_start(source, event): h = DndHandler(source, event) - if h.root: + if h.root is not None: return h else: return None @@ -143,7 +143,7 @@ class DndHandler: def __del__(self): root = self.root self.root = None - if root: + if root is not None: try: del root.__dnd except AttributeError: @@ -154,25 +154,25 @@ class DndHandler: target_widget = self.initial_widget.winfo_containing(x, y) source = self.source new_target = None - while target_widget: + while target_widget is not None: try: attr = target_widget.dnd_accept except AttributeError: pass else: new_target = attr(source, event) - if new_target: + if new_target is not None: break target_widget = target_widget.master old_target = self.target if old_target is new_target: - if old_target: + if old_target is not None: old_target.dnd_motion(source, event) else: - if old_target: + if old_target is not None: self.target = None old_target.dnd_leave(source, event) - if new_target: + if new_target is not None: new_target.dnd_enter(source, event) self.target = new_target @@ -193,7 +193,7 @@ class DndHandler: self.initial_widget.unbind("<Motion>") widget['cursor'] = self.save_cursor self.target = self.source = self.initial_widget = self.root = None - if target: + if target is not None: if commit: target.dnd_commit(source, event) else: @@ -215,9 +215,9 @@ class Icon: if canvas is self.canvas: self.canvas.coords(self.id, x, y) return - if self.canvas: + if self.canvas is not None: self.detach() - if not canvas: + if canvas is None: return label = tkinter.Label(canvas, text=self.name, borderwidth=2, relief="raised") @@ -229,7 +229,7 @@ class Icon: def detach(self): canvas = self.canvas - if not canvas: + if canvas is None: return id = self.id label = self.label diff --git a/Lib/tkinter/font.py b/Lib/tkinter/font.py index a9f79d8e45..06ed01b99c 100644 --- a/Lib/tkinter/font.py +++ b/Lib/tkinter/font.py @@ -17,10 +17,10 @@ BOLD = "bold" ITALIC = "italic" -def nametofont(name): +def nametofont(name, root=None): """Given the name of a tk named font, returns a Font representation. """ - return Font(name=name, exists=True) + return Font(name=name, exists=True, root=root) class Font: @@ -68,8 +68,8 @@ class Font: def __init__(self, root=None, font=None, name=None, exists=False, **options): - if not root: - root = tkinter._default_root + if root is None: + root = tkinter._get_default_root('use font') tk = getattr(root, 'tk', root) if font: # get actual settings corresponding to the given font @@ -183,8 +183,8 @@ class Font: def families(root=None, displayof=None): "Get font families (as a tuple)" - if not root: - root = tkinter._default_root + if root is None: + root = tkinter._get_default_root('use font.families()') args = () if displayof: args = ('-displayof', displayof) @@ -193,8 +193,8 @@ def families(root=None, displayof=None): def names(root=None): "Get names of defined fonts (as a tuple)" - if not root: - root = tkinter._default_root + if root is None: + root = tkinter._get_default_root('use font.names()') return root.tk.splitlist(root.tk.call("font", "names")) diff --git a/Lib/tkinter/simpledialog.py b/Lib/tkinter/simpledialog.py index 8524417111..a66fbd6cb9 100644 --- a/Lib/tkinter/simpledialog.py +++ b/Lib/tkinter/simpledialog.py @@ -24,10 +24,9 @@ askstring -- get a string from the user """ from tkinter import * +from tkinter import _get_temp_root, _destroy_temp_root from tkinter import messagebox -import tkinter # used at _QueryDialog for tkinter._default_root - class SimpleDialog: @@ -57,36 +56,8 @@ class SimpleDialog: b.config(relief=RIDGE, borderwidth=8) b.pack(side=LEFT, fill=BOTH, expand=1) self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window) - self._set_transient(master) - - def _set_transient(self, master, relx=0.5, rely=0.3): - widget = self.root - widget.withdraw() # Remain invisible while we figure out the geometry - widget.transient(master) - widget.update_idletasks() # Actualize geometry information - if master.winfo_ismapped(): - m_width = master.winfo_width() - m_height = master.winfo_height() - m_x = master.winfo_rootx() - m_y = master.winfo_rooty() - else: - m_width = master.winfo_screenwidth() - m_height = master.winfo_screenheight() - m_x = m_y = 0 - w_width = widget.winfo_reqwidth() - w_height = widget.winfo_reqheight() - x = m_x + (m_width - w_width) * relx - y = m_y + (m_height - w_height) * rely - if x+w_width > master.winfo_screenwidth(): - x = master.winfo_screenwidth() - w_width - elif x < 0: - x = 0 - if y+w_height > master.winfo_screenheight(): - y = master.winfo_screenheight() - w_height - elif y < 0: - y = 0 - widget.geometry("+%d+%d" % (x, y)) - widget.deiconify() # Become visible at the desired location + self.root.transient(master) + _place_window(self.root, master) def go(self): self.root.wait_visibility() @@ -128,13 +99,17 @@ class Dialog(Toplevel): title -- the dialog title ''' - Toplevel.__init__(self, parent) + master = parent + if master is None: + master = _get_temp_root() + + Toplevel.__init__(self, master) self.withdraw() # remain invisible for now - # If the master is not viewable, don't + # If the parent is not viewable, don't # make the child transient, or else it # would be opened withdrawn - if parent.winfo_viewable(): + if parent is not None and parent.winfo_viewable(): self.transient(parent) if title: @@ -150,16 +125,12 @@ class Dialog(Toplevel): self.buttonbox() - if not self.initial_focus: + if self.initial_focus is None: self.initial_focus = self self.protocol("WM_DELETE_WINDOW", self.cancel) - if self.parent is not None: - self.geometry("+%d+%d" % (parent.winfo_rootx()+50, - parent.winfo_rooty()+50)) - - self.deiconify() # become visible now + _place_window(self, parent) self.initial_focus.focus_set() @@ -172,6 +143,7 @@ class Dialog(Toplevel): '''Destroy the window''' self.initial_focus = None Toplevel.destroy(self) + _destroy_temp_root(self.master) # # construction hooks @@ -249,6 +221,37 @@ class Dialog(Toplevel): pass # override +# Place a toplevel window at the center of parent or screen +# It is a Python implementation of ::tk::PlaceWindow. +def _place_window(w, parent=None): + w.wm_withdraw() # Remain invisible while we figure out the geometry + w.update_idletasks() # Actualize geometry information + + minwidth = w.winfo_reqwidth() + minheight = w.winfo_reqheight() + maxwidth = w.winfo_vrootwidth() + maxheight = w.winfo_vrootheight() + if parent is not None and parent.winfo_ismapped(): + x = parent.winfo_rootx() + (parent.winfo_width() - minwidth) // 2 + y = parent.winfo_rooty() + (parent.winfo_height() - minheight) // 2 + vrootx = w.winfo_vrootx() + vrooty = w.winfo_vrooty() + x = min(x, vrootx + maxwidth - minwidth) + x = max(x, vrootx) + y = min(y, vrooty + maxheight - minheight) + y = max(y, vrooty) + if w._windowingsystem == 'aqua': + # Avoid the native menu bar which sits on top of everything. + y = max(y, 22) + else: + x = (w.winfo_screenwidth() - minwidth) // 2 + y = (w.winfo_screenheight() - minheight) // 2 + + w.wm_maxsize(maxwidth, maxheight) + w.wm_geometry('+%d+%d' % (x, y)) + w.wm_deiconify() # Become visible at the desired location + + # -------------------------------------------------------------------- # convenience dialogues @@ -259,9 +262,6 @@ class _QueryDialog(Dialog): minvalue = None, maxvalue = None, parent = None): - if not parent: - parent = tkinter._default_root - self.prompt = prompt self.minvalue = minvalue self.maxvalue = maxvalue diff --git a/Lib/tkinter/test/support.py b/Lib/tkinter/test/support.py index 467a0b66c2..dbc47a81e6 100644 --- a/Lib/tkinter/test/support.py +++ b/Lib/tkinter/test/support.py @@ -36,6 +36,33 @@ class AbstractTkTest: w.destroy() self.root.withdraw() + +class AbstractDefaultRootTest: + + def setUp(self): + self._old_support_default_root = tkinter._support_default_root + destroy_default_root() + tkinter._support_default_root = True + self.wantobjects = tkinter.wantobjects + + def tearDown(self): + destroy_default_root() + tkinter._default_root = None + tkinter._support_default_root = self._old_support_default_root + + def _test_widget(self, constructor): + # no master passing + x = constructor() + self.assertIsNotNone(tkinter._default_root) + self.assertIs(x.master, tkinter._default_root) + self.assertIs(x.tk, tkinter._default_root.tk) + x.destroy() + destroy_default_root() + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, constructor) + self.assertFalse(hasattr(tkinter, '_default_root')) + + def destroy_default_root(): if getattr(tkinter, '_default_root', None): tkinter._default_root.update_idletasks() diff --git a/Lib/tkinter/test/test_tkinter/test_colorchooser.py b/Lib/tkinter/test/test_tkinter/test_colorchooser.py new file mode 100644 index 0000000000..600c8cde67 --- /dev/null +++ b/Lib/tkinter/test/test_tkinter/test_colorchooser.py @@ -0,0 +1,39 @@ +import unittest +import tkinter +from test.support import requires, run_unittest, swap_attr +from tkinter.test.support import AbstractDefaultRootTest +from tkinter.commondialog import Dialog +from tkinter.colorchooser import askcolor + +requires('gui') + + +class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): + + def test_askcolor(self): + def test_callback(dialog, master): + nonlocal ismapped + master.update() + ismapped = master.winfo_ismapped() + raise ZeroDivisionError + + with swap_attr(Dialog, '_test_callback', test_callback): + ismapped = None + self.assertRaises(ZeroDivisionError, askcolor) + #askcolor() + self.assertEqual(ismapped, False) + + root = tkinter.Tk() + ismapped = None + self.assertRaises(ZeroDivisionError, askcolor) + self.assertEqual(ismapped, True) + root.destroy() + + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, askcolor) + + +tests_gui = (DefaultRootTest,) + +if __name__ == "__main__": + run_unittest(*tests_gui) diff --git a/Lib/tkinter/test/test_tkinter/test_font.py b/Lib/tkinter/test/test_tkinter/test_font.py index 6d1eea44b4..0354c5f304 100644 --- a/Lib/tkinter/test/test_tkinter/test_font.py +++ b/Lib/tkinter/test/test_tkinter/test_font.py @@ -2,7 +2,7 @@ import unittest import tkinter from tkinter import font from test.support import requires, run_unittest, gc_collect, ALWAYS_EQ -from tkinter.test.support import AbstractTkTest +from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest requires('gui') @@ -101,13 +101,58 @@ class FontTest(AbstractTkTest, unittest.TestCase): self.assertTrue(name) self.assertIn(fontname, names) + def test_nametofont(self): + testfont = font.nametofont(fontname, root=self.root) + self.assertIsInstance(testfont, font.Font) + self.assertEqual(testfont.name, fontname) + def test_repr(self): self.assertEqual( repr(self.font), f'<tkinter.font.Font object {fontname!r}>' ) -tests_gui = (FontTest, ) +class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): + + def test_families(self): + self.assertRaises(RuntimeError, font.families) + root = tkinter.Tk() + families = font.families() + self.assertIsInstance(families, tuple) + self.assertTrue(families) + for family in families: + self.assertIsInstance(family, str) + self.assertTrue(family) + root.destroy() + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, font.families) + + def test_names(self): + self.assertRaises(RuntimeError, font.names) + root = tkinter.Tk() + names = font.names() + self.assertIsInstance(names, tuple) + self.assertTrue(names) + for name in names: + self.assertIsInstance(name, str) + self.assertTrue(name) + self.assertIn(fontname, names) + root.destroy() + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, font.names) + + def test_nametofont(self): + self.assertRaises(RuntimeError, font.nametofont, fontname) + root = tkinter.Tk() + testfont = font.nametofont(fontname) + self.assertIsInstance(testfont, font.Font) + self.assertEqual(testfont.name, fontname) + root.destroy() + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, font.nametofont, fontname) + + +tests_gui = (FontTest, DefaultRootTest) if __name__ == "__main__": run_unittest(*tests_gui) diff --git a/Lib/tkinter/test/test_tkinter/test_images.py b/Lib/tkinter/test/test_tkinter/test_images.py index 6c6cb4e148..2526e92200 100644 --- a/Lib/tkinter/test/test_tkinter/test_images.py +++ b/Lib/tkinter/test/test_tkinter/test_images.py @@ -2,7 +2,7 @@ import unittest import tkinter from test import support from test.support import os_helper -from tkinter.test.support import AbstractTkTest, requires_tcl +from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest, requires_tcl support.requires('gui') @@ -20,6 +20,47 @@ class MiscTest(AbstractTkTest, unittest.TestCase): self.assertIsInstance(image_names, tuple) +class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): + + def test_image_types(self): + self.assertRaises(RuntimeError, tkinter.image_types) + root = tkinter.Tk() + image_types = tkinter.image_types() + self.assertIsInstance(image_types, tuple) + self.assertIn('photo', image_types) + self.assertIn('bitmap', image_types) + root.destroy() + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, tkinter.image_types) + + def test_image_names(self): + self.assertRaises(RuntimeError, tkinter.image_names) + root = tkinter.Tk() + image_names = tkinter.image_names() + self.assertIsInstance(image_names, tuple) + root.destroy() + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, tkinter.image_names) + + def test_image_create_bitmap(self): + self.assertRaises(RuntimeError, tkinter.BitmapImage) + root = tkinter.Tk() + image = tkinter.BitmapImage() + self.assertIn(image.name, tkinter.image_names()) + root.destroy() + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, tkinter.BitmapImage) + + def test_image_create_photo(self): + self.assertRaises(RuntimeError, tkinter.PhotoImage) + root = tkinter.Tk() + image = tkinter.PhotoImage() + self.assertIn(image.name, tkinter.image_names()) + root.destroy() + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, tkinter.PhotoImage) + + class BitmapImageTest(AbstractTkTest, unittest.TestCase): @classmethod @@ -331,7 +372,7 @@ class PhotoImageTest(AbstractTkTest, unittest.TestCase): self.assertEqual(image.transparency_get(4, 6), False) -tests_gui = (MiscTest, BitmapImageTest, PhotoImageTest,) +tests_gui = (MiscTest, DefaultRootTest, BitmapImageTest, PhotoImageTest,) if __name__ == "__main__": support.run_unittest(*tests_gui) diff --git a/Lib/tkinter/test/test_tkinter/test_messagebox.py b/Lib/tkinter/test/test_tkinter/test_messagebox.py new file mode 100644 index 0000000000..0dec08e904 --- /dev/null +++ b/Lib/tkinter/test/test_tkinter/test_messagebox.py @@ -0,0 +1,38 @@ +import unittest +import tkinter +from test.support import requires, run_unittest, swap_attr +from tkinter.test.support import AbstractDefaultRootTest +from tkinter.commondialog import Dialog +from tkinter.messagebox import showinfo + +requires('gui') + + +class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): + + def test_showinfo(self): + def test_callback(dialog, master): + nonlocal ismapped + master.update() + ismapped = master.winfo_ismapped() + raise ZeroDivisionError + + with swap_attr(Dialog, '_test_callback', test_callback): + ismapped = None + self.assertRaises(ZeroDivisionError, showinfo, "Spam", "Egg Information") + self.assertEqual(ismapped, False) + + root = tkinter.Tk() + ismapped = None + self.assertRaises(ZeroDivisionError, showinfo, "Spam", "Egg Information") + self.assertEqual(ismapped, True) + root.destroy() + + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, showinfo, "Spam", "Egg Information") + + +tests_gui = (DefaultRootTest,) + +if __name__ == "__main__": + run_unittest(*tests_gui) diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index b8eea2544f..585d81ddf9 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -1,7 +1,7 @@ import unittest import tkinter from test import support -from tkinter.test.support import AbstractTkTest +from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest support.requires('gui') @@ -241,7 +241,85 @@ class MiscTest(AbstractTkTest, unittest.TestCase): " num=3 delta=-1 focus=True" " x=10 y=20 width=300 height=200>") -tests_gui = (MiscTest, ) + def test_getboolean(self): + for v in 'true', 'yes', 'on', '1', 't', 'y', 1, True: + self.assertIs(self.root.getboolean(v), True) + for v in 'false', 'no', 'off', '0', 'f', 'n', 0, False: + self.assertIs(self.root.getboolean(v), False) + self.assertRaises(ValueError, self.root.getboolean, 'yea') + self.assertRaises(ValueError, self.root.getboolean, '') + self.assertRaises(TypeError, self.root.getboolean, None) + self.assertRaises(TypeError, self.root.getboolean, ()) + + def test_mainloop(self): + log = [] + def callback(): + log.append(1) + self.root.after(100, self.root.quit) + self.root.after(100, callback) + self.root.mainloop(1) + self.assertEqual(log, []) + self.root.mainloop(0) + self.assertEqual(log, [1]) + self.assertTrue(self.root.winfo_exists()) + + +class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): + + def test_default_root(self): + self.assertIs(tkinter._support_default_root, True) + self.assertIsNone(tkinter._default_root) + root = tkinter.Tk() + root2 = tkinter.Tk() + root3 = tkinter.Tk() + self.assertIs(tkinter._default_root, root) + root2.destroy() + self.assertIs(tkinter._default_root, root) + root.destroy() + self.assertIsNone(tkinter._default_root) + root3.destroy() + self.assertIsNone(tkinter._default_root) + + def test_no_default_root(self): + self.assertIs(tkinter._support_default_root, True) + self.assertIsNone(tkinter._default_root) + root = tkinter.Tk() + self.assertIs(tkinter._default_root, root) + tkinter.NoDefaultRoot() + self.assertIs(tkinter._support_default_root, False) + self.assertFalse(hasattr(tkinter, '_default_root')) + # repeated call is no-op + tkinter.NoDefaultRoot() + self.assertIs(tkinter._support_default_root, False) + self.assertFalse(hasattr(tkinter, '_default_root')) + root.destroy() + self.assertIs(tkinter._support_default_root, False) + self.assertFalse(hasattr(tkinter, '_default_root')) + root = tkinter.Tk() + self.assertIs(tkinter._support_default_root, False) + self.assertFalse(hasattr(tkinter, '_default_root')) + root.destroy() + + def test_getboolean(self): + self.assertRaises(RuntimeError, tkinter.getboolean, '1') + root = tkinter.Tk() + self.assertIs(tkinter.getboolean('1'), True) + self.assertRaises(ValueError, tkinter.getboolean, 'yea') + root.destroy() + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, tkinter.getboolean, '1') + + def test_mainloop(self): + self.assertRaises(RuntimeError, tkinter.mainloop) + root = tkinter.Tk() + root.after_idle(root.quit) + tkinter.mainloop() + root.destroy() + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, tkinter.mainloop) + + +tests_gui = (MiscTest, DefaultRootTest) if __name__ == "__main__": support.run_unittest(*tests_gui) diff --git a/Lib/tkinter/test/test_tkinter/test_simpledialog.py b/Lib/tkinter/test/test_tkinter/test_simpledialog.py new file mode 100644 index 0000000000..b64b854c4d --- /dev/null +++ b/Lib/tkinter/test/test_tkinter/test_simpledialog.py @@ -0,0 +1,37 @@ +import unittest +import tkinter +from test.support import requires, run_unittest, swap_attr +from tkinter.test.support import AbstractDefaultRootTest +from tkinter.simpledialog import Dialog, askinteger + +requires('gui') + + +class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): + + def test_askinteger(self): + @staticmethod + def mock_wait_window(w): + nonlocal ismapped + ismapped = w.master.winfo_ismapped() + w.destroy() + + with swap_attr(Dialog, 'wait_window', mock_wait_window): + ismapped = None + askinteger("Go To Line", "Line number") + self.assertEqual(ismapped, False) + + root = tkinter.Tk() + ismapped = None + askinteger("Go To Line", "Line number") + self.assertEqual(ismapped, True) + root.destroy() + + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, askinteger, "Go To Line", "Line number") + + +tests_gui = (DefaultRootTest,) + +if __name__ == "__main__": + run_unittest(*tests_gui) diff --git a/Lib/tkinter/test/test_tkinter/test_variables.py b/Lib/tkinter/test/test_tkinter/test_variables.py index 08b7dedcaf..63d7c21059 100644 --- a/Lib/tkinter/test/test_tkinter/test_variables.py +++ b/Lib/tkinter/test/test_tkinter/test_variables.py @@ -1,8 +1,10 @@ import unittest import gc +import tkinter from tkinter import (Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tcl, TclError) from test.support import ALWAYS_EQ +from tkinter.test.support import AbstractDefaultRootTest class Var(Variable): @@ -308,8 +310,21 @@ class TestBooleanVar(TestBase): v.get() +class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): + + def test_variable(self): + self.assertRaises(RuntimeError, Variable) + root = tkinter.Tk() + v = Variable() + v.set("value") + self.assertEqual(v.get(), "value") + root.destroy() + tkinter.NoDefaultRoot() + self.assertRaises(RuntimeError, Variable) + + tests_gui = (TestVariable, TestStringVar, TestIntVar, - TestDoubleVar, TestBooleanVar) + TestDoubleVar, TestBooleanVar, DefaultRootTest) if __name__ == "__main__": diff --git a/Lib/tkinter/test/test_tkinter/test_widgets.py b/Lib/tkinter/test/test_tkinter/test_widgets.py index b6f792d6c2..39334de8cf 100644 --- a/Lib/tkinter/test/test_tkinter/test_widgets.py +++ b/Lib/tkinter/test/test_tkinter/test_widgets.py @@ -2,11 +2,11 @@ import unittest import tkinter from tkinter import TclError import os -import sys from test.support import requires from tkinter.test.support import (tcl_version, requires_tcl, - get_tk_patchlevel, widget_eq) + get_tk_patchlevel, widget_eq, + AbstractDefaultRootTest) from tkinter.test.widget_tests import ( add_standard_options, noconv, pixels_round, AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests, @@ -22,7 +22,7 @@ def float_round(x): class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): _conv_pad_pixels = noconv - def test_class(self): + def test_configure_class(self): widget = self.create() self.assertEqual(widget['class'], widget.__class__.__name__.title()) @@ -31,7 +31,7 @@ class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): widget2 = self.create(class_='Foo') self.assertEqual(widget2['class'], 'Foo') - def test_colormap(self): + def test_configure_colormap(self): widget = self.create() self.assertEqual(widget['colormap'], '') self.checkInvalidParam(widget, 'colormap', 'new', @@ -39,7 +39,7 @@ class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): widget2 = self.create(colormap='new') self.assertEqual(widget2['colormap'], 'new') - def test_container(self): + def test_configure_container(self): widget = self.create() self.assertEqual(widget['container'], 0 if self.wantobjects else '0') self.checkInvalidParam(widget, 'container', 1, @@ -47,7 +47,7 @@ class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): widget2 = self.create(container=True) self.assertEqual(widget2['container'], 1 if self.wantobjects else '1') - def test_visual(self): + def test_configure_visual(self): widget = self.create() self.assertEqual(widget['visual'], '') self.checkInvalidParam(widget, 'visual', 'default', @@ -69,13 +69,13 @@ class ToplevelTest(AbstractToplevelTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Toplevel(self.root, **kwargs) - def test_menu(self): + def test_configure_menu(self): widget = self.create() menu = tkinter.Menu(self.root) self.checkParam(widget, 'menu', menu, eq=widget_eq) self.checkParam(widget, 'menu', '') - def test_screen(self): + def test_configure_screen(self): widget = self.create() self.assertEqual(widget['screen'], '') try: @@ -87,7 +87,7 @@ class ToplevelTest(AbstractToplevelTest, unittest.TestCase): widget2 = self.create(screen=display) self.assertEqual(widget2['screen'], display) - def test_use(self): + def test_configure_use(self): widget = self.create() self.assertEqual(widget['use'], '') parent = self.create(container=True) @@ -124,14 +124,14 @@ class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): def create(self, **kwargs): return tkinter.LabelFrame(self.root, **kwargs) - def test_labelanchor(self): + def test_configure_labelanchor(self): widget = self.create() self.checkEnumParam(widget, 'labelanchor', 'e', 'en', 'es', 'n', 'ne', 'nw', 's', 'se', 'sw', 'w', 'wn', 'ws') self.checkInvalidParam(widget, 'labelanchor', 'center') - def test_labelwidget(self): + def test_configure_labelwidget(self): widget = self.create() label = tkinter.Label(self.root, text='Mupp', name='foo') self.checkParam(widget, 'labelwidget', label, expected='.foo') @@ -141,7 +141,7 @@ class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): class AbstractLabelTest(AbstractWidgetTest, IntegerSizeTests): _conv_pixels = noconv - def test_highlightthickness(self): + def test_configure_highlightthickness(self): widget = self.create() self.checkPixelsParam(widget, 'highlightthickness', 0, 1.3, 2.6, 6, -2, '10p') @@ -179,7 +179,7 @@ class ButtonTest(AbstractLabelTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Button(self.root, **kwargs) - def test_default(self): + def test_configure_default(self): widget = self.create() self.checkEnumParam(widget, 'default', 'active', 'disabled', 'normal') @@ -204,11 +204,11 @@ class CheckbuttonTest(AbstractLabelTest, unittest.TestCase): return tkinter.Checkbutton(self.root, **kwargs) - def test_offvalue(self): + def test_configure_offvalue(self): widget = self.create() self.checkParams(widget, 'offvalue', 1, 2.3, '', 'any string') - def test_onvalue(self): + def test_configure_onvalue(self): widget = self.create() self.checkParams(widget, 'onvalue', 1, 2.3, '', 'any string') @@ -231,7 +231,7 @@ class RadiobuttonTest(AbstractLabelTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Radiobutton(self.root, **kwargs) - def test_value(self): + def test_configure_value(self): widget = self.create() self.checkParams(widget, 'value', 1, 2.3, '', 'any string') @@ -254,20 +254,19 @@ class MenubuttonTest(AbstractLabelTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Menubutton(self.root, **kwargs) - def test_direction(self): + def test_configure_direction(self): widget = self.create() self.checkEnumParam(widget, 'direction', 'above', 'below', 'flush', 'left', 'right') - def test_height(self): + def test_configure_height(self): widget = self.create() self.checkIntegerParam(widget, 'height', 100, -100, 0, conv=str) - test_highlightthickness = StandardOptionsTests.test_highlightthickness + test_configure_highlightthickness = \ + StandardOptionsTests.test_configure_highlightthickness - @unittest.skipIf(sys.platform == 'darwin', - 'crashes with Cocoa Tk (issue19733)') - def test_image(self): + def test_configure_image(self): widget = self.create() image = tkinter.PhotoImage(master=self.root, name='image1') self.checkParam(widget, 'image', image, conv=str) @@ -281,23 +280,23 @@ class MenubuttonTest(AbstractLabelTest, unittest.TestCase): if errmsg is not None: self.assertEqual(str(cm.exception), errmsg) - def test_menu(self): + def test_configure_menu(self): widget = self.create() menu = tkinter.Menu(widget, name='menu') self.checkParam(widget, 'menu', menu, eq=widget_eq) menu.destroy() - def test_padx(self): + def test_configure_padx(self): widget = self.create() self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, '12m') self.checkParam(widget, 'padx', -2, expected=0) - def test_pady(self): + def test_configure_pady(self): widget = self.create() self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, '12m') self.checkParam(widget, 'pady', -2, expected=0) - def test_width(self): + def test_configure_width(self): widget = self.create() self.checkIntegerParam(widget, 'width', 402, -402, 0, conv=str) @@ -330,18 +329,18 @@ class EntryTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Entry(self.root, **kwargs) - def test_disabledbackground(self): + def test_configure_disabledbackground(self): widget = self.create() self.checkColorParam(widget, 'disabledbackground') - def test_insertborderwidth(self): + def test_configure_insertborderwidth(self): widget = self.create(insertwidth=100) self.checkPixelsParam(widget, 'insertborderwidth', 0, 1.3, 2.6, 6, -2, '10p') # insertborderwidth is bounded above by a half of insertwidth. self.checkParam(widget, 'insertborderwidth', 60, expected=100//2) - def test_insertwidth(self): + def test_configure_insertwidth(self): widget = self.create() self.checkPixelsParam(widget, 'insertwidth', 1.3, 3.6, '10p') self.checkParam(widget, 'insertwidth', 0.1, expected=2) @@ -351,32 +350,32 @@ class EntryTest(AbstractWidgetTest, unittest.TestCase): else: self.checkParam(widget, 'insertwidth', 0.9, expected=1) - def test_invalidcommand(self): + def test_configure_invalidcommand(self): widget = self.create() self.checkCommandParam(widget, 'invalidcommand') self.checkCommandParam(widget, 'invcmd') - def test_readonlybackground(self): + def test_configure_readonlybackground(self): widget = self.create() self.checkColorParam(widget, 'readonlybackground') - def test_show(self): + def test_configure_show(self): widget = self.create() self.checkParam(widget, 'show', '*') self.checkParam(widget, 'show', '') self.checkParam(widget, 'show', ' ') - def test_state(self): + def test_configure_state(self): widget = self.create() self.checkEnumParam(widget, 'state', 'disabled', 'normal', 'readonly') - def test_validate(self): + def test_configure_validate(self): widget = self.create() self.checkEnumParam(widget, 'validate', 'all', 'key', 'focus', 'focusin', 'focusout', 'none') - def test_validatecommand(self): + def test_configure_validatecommand(self): widget = self.create() self.checkCommandParam(widget, 'validatecommand') self.checkCommandParam(widget, 'vcmd') @@ -429,25 +428,25 @@ class SpinboxTest(EntryTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Spinbox(self.root, **kwargs) - test_show = None + test_configure_show = None - def test_buttonbackground(self): + def test_configure_buttonbackground(self): widget = self.create() self.checkColorParam(widget, 'buttonbackground') - def test_buttoncursor(self): + def test_configure_buttoncursor(self): widget = self.create() self.checkCursorParam(widget, 'buttoncursor') - def test_buttondownrelief(self): + def test_configure_buttondownrelief(self): widget = self.create() self.checkReliefParam(widget, 'buttondownrelief') - def test_buttonuprelief(self): + def test_configure_buttonuprelief(self): widget = self.create() self.checkReliefParam(widget, 'buttonuprelief') - def test_format(self): + def test_configure_format(self): widget = self.create() self.checkParam(widget, 'format', '%2f') self.checkParam(widget, 'format', '%2.2f') @@ -462,25 +461,25 @@ class SpinboxTest(EntryTest, unittest.TestCase): self.checkParam(widget, 'format', '%09.200f') self.checkInvalidParam(widget, 'format', '%d') - def test_from(self): + def test_configure_from(self): widget = self.create() self.checkParam(widget, 'to', 100.0) self.checkFloatParam(widget, 'from', -10, 10.2, 11.7) self.checkInvalidParam(widget, 'from', 200, errmsg='-to value must be greater than -from value') - def test_increment(self): + def test_configure_increment(self): widget = self.create() self.checkFloatParam(widget, 'increment', -1, 1, 10.2, 12.8, 0) - def test_to(self): + def test_configure_to(self): widget = self.create() self.checkParam(widget, 'from', -100.0) self.checkFloatParam(widget, 'to', -10, 10.2, 11.7) self.checkInvalidParam(widget, 'to', -200, errmsg='-to value must be greater than -from value') - def test_values(self): + def test_configure_values(self): # XXX widget = self.create() self.assertEqual(widget['values'], '') @@ -491,7 +490,7 @@ class SpinboxTest(EntryTest, unittest.TestCase): expected='42 3.14 {} {any string}') self.checkParam(widget, 'values', '') - def test_wrap(self): + def test_configure_wrap(self): widget = self.create() self.checkBooleanParam(widget, 'wrap') @@ -557,17 +556,17 @@ class TextTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Text(self.root, **kwargs) - def test_autoseparators(self): + def test_configure_autoseparators(self): widget = self.create() self.checkBooleanParam(widget, 'autoseparators') @requires_tcl(8, 5) - def test_blockcursor(self): + def test_configure_blockcursor(self): widget = self.create() self.checkBooleanParam(widget, 'blockcursor') @requires_tcl(8, 5) - def test_endline(self): + def test_configure_endline(self): widget = self.create() text = '\n'.join('Line %d' for i in range(100)) widget.insert('end', text) @@ -580,50 +579,50 @@ class TextTest(AbstractWidgetTest, unittest.TestCase): self.checkInvalidParam(widget, 'endline', 10, errmsg='-startline must be less than or equal to -endline') - def test_height(self): + def test_configure_height(self): widget = self.create() self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, '3c') self.checkParam(widget, 'height', -100, expected=1) self.checkParam(widget, 'height', 0, expected=1) - def test_maxundo(self): + def test_configure_maxundo(self): widget = self.create() self.checkIntegerParam(widget, 'maxundo', 0, 5, -1) @requires_tcl(8, 5) - def test_inactiveselectbackground(self): + def test_configure_inactiveselectbackground(self): widget = self.create() self.checkColorParam(widget, 'inactiveselectbackground') @requires_tcl(8, 6) - def test_insertunfocussed(self): + def test_configure_insertunfocussed(self): widget = self.create() self.checkEnumParam(widget, 'insertunfocussed', 'hollow', 'none', 'solid') - def test_selectborderwidth(self): + def test_configure_selectborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p', conv=noconv, keep_orig=tcl_version >= (8, 5)) - def test_spacing1(self): + def test_configure_spacing1(self): widget = self.create() self.checkPixelsParam(widget, 'spacing1', 20, 21.4, 22.6, '0.5c') self.checkParam(widget, 'spacing1', -5, expected=0) - def test_spacing2(self): + def test_configure_spacing2(self): widget = self.create() self.checkPixelsParam(widget, 'spacing2', 5, 6.4, 7.6, '0.1c') self.checkParam(widget, 'spacing2', -1, expected=0) - def test_spacing3(self): + def test_configure_spacing3(self): widget = self.create() self.checkPixelsParam(widget, 'spacing3', 20, 21.4, 22.6, '0.5c') self.checkParam(widget, 'spacing3', -10, expected=0) @requires_tcl(8, 5) - def test_startline(self): + def test_configure_startline(self): widget = self.create() text = '\n'.join('Line %d' for i in range(100)) widget.insert('end', text) @@ -636,14 +635,14 @@ class TextTest(AbstractWidgetTest, unittest.TestCase): self.checkInvalidParam(widget, 'startline', 70, errmsg='-startline must be less than or equal to -endline') - def test_state(self): + def test_configure_state(self): widget = self.create() if tcl_version < (8, 5): self.checkParams(widget, 'state', 'disabled', 'normal') else: self.checkEnumParam(widget, 'state', 'disabled', 'normal') - def test_tabs(self): + def test_configure_tabs(self): widget = self.create() if get_tk_patchlevel() < (8, 5, 11): self.checkParam(widget, 'tabs', (10.2, 20.7, '1i', '2i'), @@ -659,21 +658,21 @@ class TextTest(AbstractWidgetTest, unittest.TestCase): keep_orig=tcl_version >= (8, 5)) @requires_tcl(8, 5) - def test_tabstyle(self): + def test_configure_tabstyle(self): widget = self.create() self.checkEnumParam(widget, 'tabstyle', 'tabular', 'wordprocessor') - def test_undo(self): + def test_configure_undo(self): widget = self.create() self.checkBooleanParam(widget, 'undo') - def test_width(self): + def test_configure_width(self): widget = self.create() self.checkIntegerParam(widget, 'width', 402) self.checkParam(widget, 'width', -402, expected=1) self.checkParam(widget, 'width', 0, expected=1) - def test_wrap(self): + def test_configure_wrap(self): widget = self.create() if tcl_version < (8, 5): self.checkParams(widget, 'wrap', 'char', 'none', 'word') @@ -711,16 +710,16 @@ class CanvasTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Canvas(self.root, **kwargs) - def test_closeenough(self): + def test_configure_closeenough(self): widget = self.create() self.checkFloatParam(widget, 'closeenough', 24, 2.4, 3.6, -3, conv=float) - def test_confine(self): + def test_configure_confine(self): widget = self.create() self.checkBooleanParam(widget, 'confine') - def test_offset(self): + def test_configure_offset(self): widget = self.create() self.assertEqual(widget['offset'], '0,0') self.checkParams(widget, 'offset', @@ -729,7 +728,7 @@ class CanvasTest(AbstractWidgetTest, unittest.TestCase): self.checkParam(widget, 'offset', '#5,6') self.checkInvalidParam(widget, 'offset', 'spam') - def test_scrollregion(self): + def test_configure_scrollregion(self): widget = self.create() self.checkParam(widget, 'scrollregion', '0 0 200 150') self.checkParam(widget, 'scrollregion', (0, 0, 200, 150), @@ -741,17 +740,17 @@ class CanvasTest(AbstractWidgetTest, unittest.TestCase): self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200)) self.checkInvalidParam(widget, 'scrollregion', (0, 0, 200, 150, 0)) - def test_state(self): + def test_configure_state(self): widget = self.create() self.checkEnumParam(widget, 'state', 'disabled', 'normal', errmsg='bad state value "{}": must be normal or disabled') - def test_xscrollincrement(self): + def test_configure_xscrollincrement(self): widget = self.create() self.checkPixelsParam(widget, 'xscrollincrement', 40, 0, 41.2, 43.6, -40, '0.5i') - def test_yscrollincrement(self): + def test_configure_yscrollincrement(self): widget = self.create() self.checkPixelsParam(widget, 'yscrollincrement', 10, 0, 11.2, 13.6, -10, '0.1i') @@ -796,26 +795,26 @@ class ListboxTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Listbox(self.root, **kwargs) - def test_activestyle(self): + def test_configure_activestyle(self): widget = self.create() self.checkEnumParam(widget, 'activestyle', 'dotbox', 'none', 'underline') - test_justify = requires_tcl(8, 6, 5)(StandardOptionsTests.test_justify) + test_justify = requires_tcl(8, 6, 5)(StandardOptionsTests.test_configure_justify) - def test_listvariable(self): + def test_configure_listvariable(self): widget = self.create() var = tkinter.DoubleVar(self.root) self.checkVariableParam(widget, 'listvariable', var) - def test_selectmode(self): + def test_configure_selectmode(self): widget = self.create() self.checkParam(widget, 'selectmode', 'single') self.checkParam(widget, 'selectmode', 'browse') self.checkParam(widget, 'selectmode', 'multiple') self.checkParam(widget, 'selectmode', 'extended') - def test_state(self): + def test_configure_state(self): widget = self.create() self.checkEnumParam(widget, 'state', 'disabled', 'normal') @@ -930,53 +929,53 @@ class ScaleTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Scale(self.root, **kwargs) - def test_bigincrement(self): + def test_configure_bigincrement(self): widget = self.create() self.checkFloatParam(widget, 'bigincrement', 12.4, 23.6, -5) - def test_digits(self): + def test_configure_digits(self): widget = self.create() self.checkIntegerParam(widget, 'digits', 5, 0) - def test_from(self): + def test_configure_from(self): widget = self.create() conv = False if get_tk_patchlevel() >= (8, 6, 10) else float_round self.checkFloatParam(widget, 'from', 100, 14.9, 15.1, conv=conv) - def test_label(self): + def test_configure_label(self): widget = self.create() self.checkParam(widget, 'label', 'any string') self.checkParam(widget, 'label', '') - def test_length(self): + def test_configure_length(self): widget = self.create() self.checkPixelsParam(widget, 'length', 130, 131.2, 135.6, '5i') - def test_resolution(self): + def test_configure_resolution(self): widget = self.create() self.checkFloatParam(widget, 'resolution', 4.2, 0, 6.7, -2) - def test_showvalue(self): + def test_configure_showvalue(self): widget = self.create() self.checkBooleanParam(widget, 'showvalue') - def test_sliderlength(self): + def test_configure_sliderlength(self): widget = self.create() self.checkPixelsParam(widget, 'sliderlength', 10, 11.2, 15.6, -3, '3m') - def test_sliderrelief(self): + def test_configure_sliderrelief(self): widget = self.create() self.checkReliefParam(widget, 'sliderrelief') - def test_tickinterval(self): + def test_configure_tickinterval(self): widget = self.create() self.checkFloatParam(widget, 'tickinterval', 1, 4.3, 7.6, 0, conv=float_round) self.checkParam(widget, 'tickinterval', -2, expected=2, conv=float_round) - def test_to(self): + def test_configure_to(self): widget = self.create() self.checkFloatParam(widget, 'to', 300, 14.9, 15.1, -10, conv=float_round) @@ -1000,15 +999,15 @@ class ScrollbarTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Scrollbar(self.root, **kwargs) - def test_activerelief(self): + def test_configure_activerelief(self): widget = self.create() self.checkReliefParam(widget, 'activerelief') - def test_elementborderwidth(self): + def test_configure_elementborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'elementborderwidth', 4.3, 5.6, -2, '1m') - def test_orient(self): + def test_configure_orient(self): widget = self.create() self.checkEnumParam(widget, 'orient', 'vertical', 'horizontal', errmsg='bad orientation "{}": must be vertical or horizontal') @@ -1049,63 +1048,63 @@ class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return tkinter.PanedWindow(self.root, **kwargs) - def test_handlepad(self): + def test_configure_handlepad(self): widget = self.create() self.checkPixelsParam(widget, 'handlepad', 5, 6.4, 7.6, -3, '1m') - def test_handlesize(self): + def test_configure_handlesize(self): widget = self.create() self.checkPixelsParam(widget, 'handlesize', 8, 9.4, 10.6, -3, '2m', conv=noconv) - def test_height(self): + def test_configure_height(self): widget = self.create() self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '1i', conv=noconv) - def test_opaqueresize(self): + def test_configure_opaqueresize(self): widget = self.create() self.checkBooleanParam(widget, 'opaqueresize') @requires_tcl(8, 6, 5) - def test_proxybackground(self): + def test_configure_proxybackground(self): widget = self.create() self.checkColorParam(widget, 'proxybackground') @requires_tcl(8, 6, 5) - def test_proxyborderwidth(self): + def test_configure_proxyborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'proxyborderwidth', 0, 1.3, 2.9, 6, -2, '10p', conv=noconv) @requires_tcl(8, 6, 5) - def test_proxyrelief(self): + def test_configure_proxyrelief(self): widget = self.create() self.checkReliefParam(widget, 'proxyrelief') - def test_sashcursor(self): + def test_configure_sashcursor(self): widget = self.create() self.checkCursorParam(widget, 'sashcursor') - def test_sashpad(self): + def test_configure_sashpad(self): widget = self.create() self.checkPixelsParam(widget, 'sashpad', 8, 1.3, 2.6, -2, '2m') - def test_sashrelief(self): + def test_configure_sashrelief(self): widget = self.create() self.checkReliefParam(widget, 'sashrelief') - def test_sashwidth(self): + def test_configure_sashwidth(self): widget = self.create() self.checkPixelsParam(widget, 'sashwidth', 10, 11.1, 15.6, -3, '1m', conv=noconv) - def test_showhandle(self): + def test_configure_showhandle(self): widget = self.create() self.checkBooleanParam(widget, 'showhandle') - def test_width(self): + def test_configure_width(self): widget = self.create() self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i', conv=noconv) @@ -1224,23 +1223,23 @@ class MenuTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Menu(self.root, **kwargs) - def test_postcommand(self): + def test_configure_postcommand(self): widget = self.create() self.checkCommandParam(widget, 'postcommand') - def test_tearoff(self): + def test_configure_tearoff(self): widget = self.create() self.checkBooleanParam(widget, 'tearoff') - def test_tearoffcommand(self): + def test_configure_tearoffcommand(self): widget = self.create() self.checkCommandParam(widget, 'tearoffcommand') - def test_title(self): + def test_configure_title(self): widget = self.create() self.checkParam(widget, 'title', 'any string') - def test_type(self): + def test_configure_type(self): widget = self.create() self.checkEnumParam(widget, 'type', 'normal', 'tearoff', 'menubar') @@ -1293,17 +1292,26 @@ class MessageTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return tkinter.Message(self.root, **kwargs) - def test_aspect(self): + def test_configure_aspect(self): widget = self.create() self.checkIntegerParam(widget, 'aspect', 250, 0, -300) +class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): + + def test_frame(self): + self._test_widget(tkinter.Frame) + + def test_label(self): + self._test_widget(tkinter.Label) + + tests_gui = ( ButtonTest, CanvasTest, CheckbuttonTest, EntryTest, FrameTest, LabelFrameTest,LabelTest, ListboxTest, MenubuttonTest, MenuTest, MessageTest, OptionMenuTest, PanedWindowTest, RadiobuttonTest, ScaleTest, ScrollbarTest, - SpinboxTest, TextTest, ToplevelTest, + SpinboxTest, TextTest, ToplevelTest, DefaultRootTest, ) if __name__ == '__main__': diff --git a/Lib/tkinter/test/test_ttk/test_extensions.py b/Lib/tkinter/test/test_ttk/test_extensions.py index a45f882bb0..1a70e0befe 100644 --- a/Lib/tkinter/test/test_ttk/test_extensions.py +++ b/Lib/tkinter/test/test_ttk/test_extensions.py @@ -2,8 +2,8 @@ import sys import unittest import tkinter from tkinter import ttk -from test.support import requires, run_unittest, swap_attr -from tkinter.test.support import AbstractTkTest, destroy_default_root +from test.support import requires, run_unittest +from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest requires('gui') @@ -46,20 +46,6 @@ class LabeledScaleTest(AbstractTkTest, unittest.TestCase): if hasattr(sys, 'last_type'): self.assertNotEqual(sys.last_type, tkinter.TclError) - - def test_initialization_no_master(self): - # no master passing - with swap_attr(tkinter, '_default_root', None), \ - swap_attr(tkinter, '_support_default_root', True): - try: - x = ttk.LabeledScale() - self.assertIsNotNone(tkinter._default_root) - self.assertEqual(x.master, tkinter._default_root) - self.assertEqual(x.tk, tkinter._default_root.tk) - x.destroy() - finally: - destroy_default_root() - def test_initialization(self): # master passing master = tkinter.Frame(self.root) @@ -114,7 +100,6 @@ class LabeledScaleTest(AbstractTkTest, unittest.TestCase): def test_horizontal_range(self): lscale = ttk.LabeledScale(self.root, from_=0, to=10) lscale.pack() - lscale.wait_visibility() lscale.update() linfo_1 = lscale.label.place_info() @@ -144,7 +129,6 @@ class LabeledScaleTest(AbstractTkTest, unittest.TestCase): def test_variable_change(self): x = ttk.LabeledScale(self.root) x.pack() - x.wait_visibility() x.update() curr_xcoord = x.scale.coords()[0] @@ -187,7 +171,6 @@ class LabeledScaleTest(AbstractTkTest, unittest.TestCase): def test_resize(self): x = ttk.LabeledScale(self.root) x.pack(expand=True, fill='both') - x.wait_visibility() x.update() width, height = x.master.winfo_width(), x.master.winfo_height() @@ -268,7 +251,6 @@ class OptionMenuTest(AbstractTkTest, unittest.TestCase): # check that variable is updated correctly optmenu.pack() - optmenu.wait_visibility() optmenu['menu'].invoke(0) self.assertEqual(optmenu._variable.get(), items[0]) @@ -299,9 +281,7 @@ class OptionMenuTest(AbstractTkTest, unittest.TestCase): textvar2 = tkinter.StringVar(self.root) optmenu2 = ttk.OptionMenu(self.root, textvar2, default, *items) optmenu.pack() - optmenu.wait_visibility() optmenu2.pack() - optmenu2.wait_visibility() optmenu['menu'].invoke(1) optmenu2['menu'].invoke(2) optmenu_stringvar_name = optmenu['menu'].entrycget(0, 'variable') @@ -317,7 +297,13 @@ class OptionMenuTest(AbstractTkTest, unittest.TestCase): optmenu2.destroy() -tests_gui = (LabeledScaleTest, OptionMenuTest) +class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): + + def test_labeledscale(self): + self._test_widget(ttk.LabeledScale) + + +tests_gui = (LabeledScaleTest, OptionMenuTest, DefaultRootTest) if __name__ == "__main__": run_unittest(*tests_gui) diff --git a/Lib/tkinter/test/test_ttk/test_functions.py b/Lib/tkinter/test/test_ttk/test_functions.py index f8e69a9f41..5c23d6fecf 100644 --- a/Lib/tkinter/test/test_ttk/test_functions.py +++ b/Lib/tkinter/test/test_ttk/test_functions.py @@ -137,6 +137,9 @@ class InternalFunctionsTest(unittest.TestCase): result = ttk._format_mapdict(opts) self.assertEqual(result, ('-üñÃćódè', 'á vãl')) + self.assertEqual(ttk._format_mapdict({'opt': [('value',)]}), + ('-opt', '{} value')) + # empty states valid = {'opt': [('', '', 'hi')]} self.assertEqual(ttk._format_mapdict(valid), ('-opt', '{ } hi')) @@ -159,10 +162,6 @@ class InternalFunctionsTest(unittest.TestCase): opts = {'a': None} self.assertRaises(TypeError, ttk._format_mapdict, opts) - # items in the value must have size >= 2 - self.assertRaises(IndexError, ttk._format_mapdict, - {'a': [('invalid', )]}) - def test_format_elemcreate(self): self.assertTrue(ttk._format_elemcreate(None), (None, ())) diff --git a/Lib/tkinter/test/test_ttk/test_style.py b/Lib/tkinter/test/test_ttk/test_style.py index 3537536d81..38d70d7a89 100644 --- a/Lib/tkinter/test/test_ttk/test_style.py +++ b/Lib/tkinter/test/test_ttk/test_style.py @@ -1,11 +1,23 @@ import unittest +import sys import tkinter from tkinter import ttk +from test import support from test.support import requires, run_unittest from tkinter.test.support import AbstractTkTest requires('gui') +CLASS_NAMES = [ + '.', 'ComboboxPopdownFrame', 'Heading', + 'Horizontal.TProgressbar', 'Horizontal.TScale', 'Item', 'Sash', + 'TButton', 'TCheckbutton', 'TCombobox', 'TEntry', + 'TLabelframe', 'TLabelframe.Label', 'TMenubutton', + 'TNotebook', 'TNotebook.Tab', 'Toolbutton', 'TProgressbar', + 'TRadiobutton', 'Treeview', 'TScale', 'TScrollbar', 'TSpinbox', + 'Vertical.TProgressbar', 'Vertical.TScale' +] + class StyleTest(AbstractTkTest, unittest.TestCase): def setUp(self): @@ -23,11 +35,36 @@ class StyleTest(AbstractTkTest, unittest.TestCase): def test_map(self): style = self.style - style.map('TButton', background=[('active', 'background', 'blue')]) - self.assertEqual(style.map('TButton', 'background'), - [('active', 'background', 'blue')] if self.wantobjects else - [('active background', 'blue')]) - self.assertIsInstance(style.map('TButton'), dict) + + # Single state + for states in ['active'], [('active',)]: + with self.subTest(states=states): + style.map('TButton', background=[(*states, 'white')]) + expected = [('active', 'white')] + self.assertEqual(style.map('TButton', 'background'), expected) + m = style.map('TButton') + self.assertIsInstance(m, dict) + self.assertEqual(m['background'], expected) + + # Multiple states + for states in ['pressed', '!disabled'], ['pressed !disabled'], [('pressed', '!disabled')]: + with self.subTest(states=states): + style.map('TButton', background=[(*states, 'black')]) + expected = [('pressed', '!disabled', 'black')] + self.assertEqual(style.map('TButton', 'background'), expected) + m = style.map('TButton') + self.assertIsInstance(m, dict) + self.assertEqual(m['background'], expected) + + # Default state + for states in [], [''], [()]: + with self.subTest(states=states): + style.map('TButton', background=[(*states, 'grey')]) + expected = [('grey',)] + self.assertEqual(style.map('TButton', 'background'), expected) + m = style.map('TButton') + self.assertIsInstance(m, dict) + self.assertEqual(m['background'], expected) def test_lookup(self): @@ -86,6 +123,58 @@ class StyleTest(AbstractTkTest, unittest.TestCase): self.style.theme_use(curr_theme) + def test_configure_custom_copy(self): + style = self.style + + curr_theme = self.style.theme_use() + self.addCleanup(self.style.theme_use, curr_theme) + for theme in self.style.theme_names(): + self.style.theme_use(theme) + for name in CLASS_NAMES: + default = style.configure(name) + if not default: + continue + with self.subTest(theme=theme, name=name): + if support.verbose >= 2: + print('configure', theme, name, default) + if (theme in ('vista', 'xpnative') + and sys.getwindowsversion()[:2] == (6, 1)): + # Fails on the Windows 7 buildbot + continue + newname = f'C.{name}' + self.assertEqual(style.configure(newname), None) + style.configure(newname, **default) + self.assertEqual(style.configure(newname), default) + for key, value in default.items(): + self.assertEqual(style.configure(newname, key), value) + + + def test_map_custom_copy(self): + style = self.style + + curr_theme = self.style.theme_use() + self.addCleanup(self.style.theme_use, curr_theme) + for theme in self.style.theme_names(): + self.style.theme_use(theme) + for name in CLASS_NAMES: + default = style.map(name) + if not default: + continue + with self.subTest(theme=theme, name=name): + if support.verbose >= 2: + print('map', theme, name, default) + if (theme in ('vista', 'xpnative') + and sys.getwindowsversion()[:2] == (6, 1)): + # Fails on the Windows 7 buildbot + continue + newname = f'C.{name}' + self.assertEqual(style.map(newname), {}) + style.map(newname, **default) + self.assertEqual(style.map(newname), default) + for key, value in default.items(): + self.assertEqual(style.map(newname, key), value) + + tests_gui = (StyleTest, ) if __name__ == "__main__": diff --git a/Lib/tkinter/test/test_ttk/test_widgets.py b/Lib/tkinter/test/test_ttk/test_widgets.py index 2598bc6765..1fac83a004 100644 --- a/Lib/tkinter/test/test_ttk/test_widgets.py +++ b/Lib/tkinter/test/test_ttk/test_widgets.py @@ -6,7 +6,7 @@ import sys from tkinter.test.test_ttk.test_functions import MockTclObj from tkinter.test.support import (AbstractTkTest, tcl_version, get_tk_patchlevel, - simulate_mouse_click) + simulate_mouse_click, AbstractDefaultRootTest) from tkinter.test.widget_tests import (add_standard_options, noconv, AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests, setUpModule) @@ -16,7 +16,7 @@ requires('gui') class StandardTtkOptionsTests(StandardOptionsTests): - def test_class(self): + def test_configure_class(self): widget = self.create() self.assertEqual(widget['class'], '') errmsg='attempt to change read-only option' @@ -26,7 +26,7 @@ class StandardTtkOptionsTests(StandardOptionsTests): widget2 = self.create(class_='Foo') self.assertEqual(widget2['class'], 'Foo') - def test_padding(self): + def test_configure_padding(self): widget = self.create() self.checkParam(widget, 'padding', 0, expected=('0',)) self.checkParam(widget, 'padding', 5, expected=('5',)) @@ -38,7 +38,7 @@ class StandardTtkOptionsTests(StandardOptionsTests): self.checkParam(widget, 'padding', ('5p', '6p', '7p', '8p')) self.checkParam(widget, 'padding', (), expected='') - def test_style(self): + def test_configure_style(self): widget = self.create() self.assertEqual(widget['style'], '') errmsg = 'Layout Foo not found' @@ -60,11 +60,10 @@ class WidgetTest(AbstractTkTest, unittest.TestCase): super().setUp() self.widget = ttk.Button(self.root, width=0, text="Text") self.widget.pack() - self.widget.wait_visibility() def test_identify(self): - self.widget.update_idletasks() + self.widget.update() self.assertEqual(self.widget.identify( int(self.widget.winfo_width() / 2), int(self.widget.winfo_height() / 2) @@ -140,14 +139,14 @@ class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): def create(self, **kwargs): return ttk.LabelFrame(self.root, **kwargs) - def test_labelanchor(self): + def test_configure_labelanchor(self): widget = self.create() self.checkEnumParam(widget, 'labelanchor', 'e', 'en', 'es', 'n', 'ne', 'nw', 's', 'se', 'sw', 'w', 'wn', 'ws', errmsg='Bad label anchor specification {}') self.checkInvalidParam(widget, 'labelanchor', 'center') - def test_labelwidget(self): + def test_configure_labelwidget(self): widget = self.create() label = ttk.Label(self.root, text='Mupp', name='foo') self.checkParam(widget, 'labelwidget', label, expected='.foo') @@ -169,17 +168,17 @@ class AbstractLabelTest(AbstractWidgetTest): self.checkInvalidParam(widget, name, 'spam', errmsg='image "spam" doesn\'t exist') - def test_compound(self): + def test_configure_compound(self): widget = self.create() self.checkEnumParam(widget, 'compound', 'none', 'text', 'image', 'center', 'top', 'bottom', 'left', 'right') - def test_state(self): + def test_configure_state(self): widget = self.create() self.checkParams(widget, 'state', 'active', 'disabled', 'normal') - def test_width(self): + def test_configure_width(self): widget = self.create() self.checkParams(widget, 'width', 402, -402, 0) @@ -198,7 +197,7 @@ class LabelTest(AbstractLabelTest, unittest.TestCase): def create(self, **kwargs): return ttk.Label(self.root, **kwargs) - def test_font(self): + def test_configure_font(self): widget = self.create() self.checkParam(widget, 'font', '-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*') @@ -216,7 +215,7 @@ class ButtonTest(AbstractLabelTest, unittest.TestCase): def create(self, **kwargs): return ttk.Button(self.root, **kwargs) - def test_default(self): + def test_configure_default(self): widget = self.create() self.checkEnumParam(widget, 'default', 'normal', 'active', 'disabled') @@ -241,11 +240,11 @@ class CheckbuttonTest(AbstractLabelTest, unittest.TestCase): def create(self, **kwargs): return ttk.Checkbutton(self.root, **kwargs) - def test_offvalue(self): + def test_configure_offvalue(self): widget = self.create() self.checkParams(widget, 'offvalue', 1, 2.3, '', 'any string') - def test_onvalue(self): + def test_configure_onvalue(self): widget = self.create() self.checkParams(widget, 'onvalue', 1, 2.3, '', 'any string') @@ -293,27 +292,27 @@ class EntryTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return ttk.Entry(self.root, **kwargs) - def test_invalidcommand(self): + def test_configure_invalidcommand(self): widget = self.create() self.checkCommandParam(widget, 'invalidcommand') - def test_show(self): + def test_configure_show(self): widget = self.create() self.checkParam(widget, 'show', '*') self.checkParam(widget, 'show', '') self.checkParam(widget, 'show', ' ') - def test_state(self): + def test_configure_state(self): widget = self.create() self.checkParams(widget, 'state', 'disabled', 'normal', 'readonly') - def test_validate(self): + def test_configure_validate(self): widget = self.create() self.checkEnumParam(widget, 'validate', 'all', 'key', 'focus', 'focusin', 'focusout', 'none') - def test_validatecommand(self): + def test_configure_validatecommand(self): widget = self.create() self.checkCommandParam(widget, 'validatecommand') @@ -326,8 +325,7 @@ class EntryTest(AbstractWidgetTest, unittest.TestCase): def test_identify(self): self.entry.pack() - self.entry.wait_visibility() - self.entry.update_idletasks() + self.entry.update() # bpo-27313: macOS Cocoa widget differs from X, allow either if sys.platform == 'darwin': @@ -431,17 +429,18 @@ class ComboboxTest(EntryTest, unittest.TestCase): def create(self, **kwargs): return ttk.Combobox(self.root, **kwargs) - def test_height(self): + def test_configure_height(self): widget = self.create() self.checkParams(widget, 'height', 100, 101.2, 102.6, -100, 0, '1i') def _show_drop_down_listbox(self): width = self.combo.winfo_width() - self.combo.event_generate('<ButtonPress-1>', x=width - 5, y=5) - self.combo.event_generate('<ButtonRelease-1>', x=width - 5, y=5) + x, y = width - 5, 5 + self.assertRegex(self.combo.identify(x, y), r'.*downarrow\Z') + self.combo.event_generate('<ButtonPress-1>', x=x, y=y) + self.combo.event_generate('<ButtonRelease-1>', x=x, y=y) self.combo.update_idletasks() - def test_virtual_event(self): success = [] @@ -449,7 +448,7 @@ class ComboboxTest(EntryTest, unittest.TestCase): self.combo.bind('<<ComboboxSelected>>', lambda evt: success.append(True)) self.combo.pack() - self.combo.wait_visibility() + self.combo.update() height = self.combo.winfo_height() self._show_drop_down_listbox() @@ -460,12 +459,12 @@ class ComboboxTest(EntryTest, unittest.TestCase): self.assertTrue(success) - def test_postcommand(self): + def test_configure_postcommand(self): success = [] self.combo['postcommand'] = lambda: success.append(True) self.combo.pack() - self.combo.wait_visibility() + self.combo.update() self._show_drop_down_listbox() self.assertTrue(success) @@ -476,7 +475,7 @@ class ComboboxTest(EntryTest, unittest.TestCase): self.assertEqual(len(success), 1) - def test_values(self): + def test_configure_values(self): def check_get_current(getval, currval): self.assertEqual(self.combo.get(), getval) self.assertEqual(self.combo.current(), currval) @@ -552,7 +551,7 @@ class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return ttk.PanedWindow(self.root, **kwargs) - def test_orient(self): + def test_configure_orient(self): widget = self.create() self.assertEqual(str(widget['orient']), 'vertical') errmsg='attempt to change read-only option' @@ -665,7 +664,6 @@ class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): self.assertRaises(tkinter.TclError, self.paned.sashpos, 1) self.paned.pack(expand=True, fill='both') - self.paned.wait_visibility() curr_pos = self.paned.sashpos(0) self.paned.sashpos(0, 1000) @@ -686,11 +684,11 @@ class RadiobuttonTest(AbstractLabelTest, unittest.TestCase): def create(self, **kwargs): return ttk.Radiobutton(self.root, **kwargs) - def test_value(self): + def test_configure_value(self): widget = self.create() self.checkParams(widget, 'value', 1, 2.3, '', 'any string') - def test_invoke(self): + def test_configure_invoke(self): success = [] def cb_test(): success.append(1) @@ -741,7 +739,7 @@ class MenubuttonTest(AbstractLabelTest, unittest.TestCase): self.checkEnumParam(widget, 'direction', 'above', 'below', 'left', 'right', 'flush') - def test_menu(self): + def test_configure_menu(self): widget = self.create() menu = tkinter.Menu(widget, name='menu') self.checkParam(widget, 'menu', menu, conv=str) @@ -766,19 +764,19 @@ class ScaleTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return ttk.Scale(self.root, **kwargs) - def test_from(self): + def test_configure_from(self): widget = self.create() self.checkFloatParam(widget, 'from', 100, 14.9, 15.1, conv=False) - def test_length(self): + def test_configure_length(self): widget = self.create() self.checkPixelsParam(widget, 'length', 130, 131.2, 135.6, '5i') - def test_to(self): + def test_configure_to(self): widget = self.create() self.checkFloatParam(widget, 'to', 300, 14.9, 15.1, -10, conv=False) - def test_value(self): + def test_configure_value(self): widget = self.create() self.checkFloatParam(widget, 'value', 300, 14.9, 15.1, -10, conv=False) @@ -868,23 +866,23 @@ class ProgressbarTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return ttk.Progressbar(self.root, **kwargs) - def test_length(self): + def test_configure_length(self): widget = self.create() self.checkPixelsParam(widget, 'length', 100.1, 56.7, '2i') - def test_maximum(self): + def test_configure_maximum(self): widget = self.create() self.checkFloatParam(widget, 'maximum', 150.2, 77.7, 0, -10, conv=False) - def test_mode(self): + def test_configure_mode(self): widget = self.create() self.checkEnumParam(widget, 'mode', 'determinate', 'indeterminate') - def test_phase(self): + def test_configure_phase(self): # XXX pass - def test_value(self): + def test_configure_value(self): widget = self.create() self.checkFloatParam(widget, 'value', 150.2, 77.7, 0, -10, conv=False) @@ -933,7 +931,7 @@ class NotebookTest(AbstractWidgetTest, unittest.TestCase): self.nb.add(self.child1, text='a') self.nb.pack() - self.nb.wait_visibility() + self.nb.update() if sys.platform == 'darwin': tb_idx = "@20,5" else: @@ -1041,7 +1039,7 @@ class NotebookTest(AbstractWidgetTest, unittest.TestCase): def test_select(self): self.nb.pack() - self.nb.wait_visibility() + self.nb.update() success = [] tab_changed = [] @@ -1073,7 +1071,7 @@ class NotebookTest(AbstractWidgetTest, unittest.TestCase): self.assertEqual(self.nb.tab(self.child1, 'text'), 'abc') - def test_tabs(self): + def test_configure_tabs(self): self.assertEqual(len(self.nb.tabs()), 2) self.nb.forget(self.child1) @@ -1084,10 +1082,11 @@ class NotebookTest(AbstractWidgetTest, unittest.TestCase): def test_traversal(self): self.nb.pack() - self.nb.wait_visibility() + self.nb.update() self.nb.select(0) + self.assertEqual(self.nb.identify(5, 5), 'focus') simulate_mouse_click(self.nb, 5, 5) self.nb.focus_force() self.nb.event_generate('<Control-Tab>') @@ -1102,6 +1101,7 @@ class NotebookTest(AbstractWidgetTest, unittest.TestCase): self.nb.tab(self.child1, text='a', underline=0) self.nb.enable_traversal() self.nb.focus_force() + self.assertEqual(self.nb.identify(5, 5), 'focus') simulate_mouse_click(self.nb, 5, 5) if sys.platform == 'darwin': self.nb.event_generate('<Option-a>') @@ -1132,6 +1132,7 @@ class SpinboxTest(EntryTest, unittest.TestCase): height = self.spin.winfo_height() x = width - 5 y = height//2 - 5 + self.assertRegex(self.spin.identify(x, y), r'.*uparrow\Z') self.spin.event_generate('<ButtonPress-1>', x=x, y=y) self.spin.event_generate('<ButtonRelease-1>', x=x, y=y) self.spin.update_idletasks() @@ -1141,11 +1142,12 @@ class SpinboxTest(EntryTest, unittest.TestCase): height = self.spin.winfo_height() x = width - 5 y = height//2 + 4 + self.assertRegex(self.spin.identify(x, y), r'.*downarrow\Z') self.spin.event_generate('<ButtonPress-1>', x=x, y=y) self.spin.event_generate('<ButtonRelease-1>', x=x, y=y) self.spin.update_idletasks() - def test_command(self): + def test_configure_command(self): success = [] self.spin['command'] = lambda: success.append(True) @@ -1165,7 +1167,7 @@ class SpinboxTest(EntryTest, unittest.TestCase): self.spin.update() self.assertEqual(len(success), 2) - def test_to(self): + def test_configure_to(self): self.spin['from'] = 0 self.spin['to'] = 5 self.spin.set(4) @@ -1177,7 +1179,7 @@ class SpinboxTest(EntryTest, unittest.TestCase): self._click_increment_arrow() # 5 self.assertEqual(self.spin.get(), '5') - def test_from(self): + def test_configure_from(self): self.spin['from'] = 1 self.spin['to'] = 10 self.spin.set(2) @@ -1187,7 +1189,7 @@ class SpinboxTest(EntryTest, unittest.TestCase): self._click_decrement_arrow() # 1 self.assertEqual(self.spin.get(), '1') - def test_increment(self): + def test_configure_increment(self): self.spin['from'] = 0 self.spin['to'] = 10 self.spin['increment'] = 4 @@ -1201,7 +1203,7 @@ class SpinboxTest(EntryTest, unittest.TestCase): self._click_decrement_arrow() # 3 self.assertEqual(self.spin.get(), '3') - def test_format(self): + def test_configure_format(self): self.spin.set(1) self.spin['format'] = '%10.3f' self.spin.update() @@ -1218,7 +1220,7 @@ class SpinboxTest(EntryTest, unittest.TestCase): self.assertTrue('.' not in value) self.assertEqual(len(value), 1) - def test_wrap(self): + def test_configure_wrap(self): self.spin['to'] = 10 self.spin['from'] = 1 self.spin.set(1) @@ -1237,7 +1239,7 @@ class SpinboxTest(EntryTest, unittest.TestCase): self._click_decrement_arrow() self.assertEqual(self.spin.get(), '1') - def test_values(self): + def test_configure_values(self): self.assertEqual(self.spin['values'], () if tcl_version < (8, 5) else '') self.checkParam(self.spin, 'values', 'mon tue wed thur', @@ -1297,14 +1299,14 @@ class TreeviewTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return ttk.Treeview(self.root, **kwargs) - def test_columns(self): + def test_configure_columns(self): widget = self.create() self.checkParam(widget, 'columns', 'a b c', expected=('a', 'b', 'c')) self.checkParam(widget, 'columns', ('a', 'b', 'c')) self.checkParam(widget, 'columns', '') - def test_displaycolumns(self): + def test_configure_displaycolumns(self): widget = self.create() widget['columns'] = ('a', 'b', 'c') self.checkParam(widget, 'displaycolumns', 'b a c', @@ -1320,17 +1322,17 @@ class TreeviewTest(AbstractWidgetTest, unittest.TestCase): self.checkInvalidParam(widget, 'displaycolumns', (1, -2), errmsg='Column index -2 out of bounds') - def test_height(self): + def test_configure_height(self): widget = self.create() self.checkPixelsParam(widget, 'height', 100, -100, 0, '3c', conv=False) self.checkPixelsParam(widget, 'height', 101.2, 102.6, conv=noconv) - def test_selectmode(self): + def test_configure_selectmode(self): widget = self.create() self.checkEnumParam(widget, 'selectmode', 'none', 'browse', 'extended') - def test_show(self): + def test_configure_show(self): widget = self.create() self.checkParam(widget, 'show', 'tree headings', expected=('tree', 'headings')) @@ -1342,7 +1344,6 @@ class TreeviewTest(AbstractWidgetTest, unittest.TestCase): def test_bbox(self): self.tv.pack() self.assertEqual(self.tv.bbox(''), '') - self.tv.wait_visibility() self.tv.update() item_id = self.tv.insert('', 'end') @@ -1530,13 +1531,15 @@ class TreeviewTest(AbstractWidgetTest, unittest.TestCase): def test_heading_callback(self): def simulate_heading_click(x, y): + if tcl_version >= (8, 6): + self.assertEqual(self.tv.identify_column(x), '#0') + self.assertEqual(self.tv.identify_region(x, y), 'heading') simulate_mouse_click(self.tv, x, y) self.tv.update() success = [] # no success for now self.tv.pack() - self.tv.wait_visibility() self.tv.heading('#0', command=lambda: success.append(True)) self.tv.column('#0', width=100) self.tv.update() @@ -1784,7 +1787,6 @@ class TreeviewTest(AbstractWidgetTest, unittest.TestCase): lambda evt: events.append(2)) self.tv.pack() - self.tv.wait_visibility() self.tv.update() pos_y = set() @@ -1858,12 +1860,22 @@ class SizegripTest(AbstractWidgetTest, unittest.TestCase): def create(self, **kwargs): return ttk.Sizegrip(self.root, **kwargs) + +class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): + + def test_frame(self): + self._test_widget(ttk.Frame) + + def test_label(self): + self._test_widget(ttk.Label) + + tests_gui = ( ButtonTest, CheckbuttonTest, ComboboxTest, EntryTest, FrameTest, LabelFrameTest, LabelTest, MenubuttonTest, NotebookTest, PanedWindowTest, ProgressbarTest, RadiobuttonTest, ScaleTest, ScrollbarTest, SeparatorTest, - SizegripTest, SpinboxTest, TreeviewTest, WidgetTest, + SizegripTest, SpinboxTest, TreeviewTest, WidgetTest, DefaultRootTest, ) if __name__ == "__main__": diff --git a/Lib/tkinter/test/widget_tests.py b/Lib/tkinter/test/widget_tests.py index b42ff52178..9702ff4530 100644 --- a/Lib/tkinter/test/widget_tests.py +++ b/Lib/tkinter/test/widget_tests.py @@ -1,7 +1,6 @@ # Common tests for test_tkinter/test_widgets.py and test_ttk/test_widgets.py import unittest -import sys import tkinter from tkinter.test.support import (AbstractTkTest, tcl_version, requires_tcl, get_tk_patchlevel, pixels_conv, tcl_obj_eq) @@ -243,31 +242,31 @@ class StandardOptionsTests: 'underline', 'wraplength', 'xscrollcommand', 'yscrollcommand', ) - def test_activebackground(self): + def test_configure_activebackground(self): widget = self.create() self.checkColorParam(widget, 'activebackground') - def test_activeborderwidth(self): + def test_configure_activeborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'activeborderwidth', 0, 1.3, 2.9, 6, -2, '10p') - def test_activeforeground(self): + def test_configure_activeforeground(self): widget = self.create() self.checkColorParam(widget, 'activeforeground') - def test_anchor(self): + def test_configure_anchor(self): widget = self.create() self.checkEnumParam(widget, 'anchor', 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'center') - def test_background(self): + def test_configure_background(self): widget = self.create() self.checkColorParam(widget, 'background') if 'bg' in self.OPTIONS: self.checkColorParam(widget, 'bg') - def test_bitmap(self): + def test_configure_bitmap(self): widget = self.create() self.checkParam(widget, 'bitmap', 'questhead') self.checkParam(widget, 'bitmap', 'gray50') @@ -280,90 +279,88 @@ class StandardOptionsTests: self.checkInvalidParam(widget, 'bitmap', 'spam', errmsg='bitmap "spam" not defined') - def test_borderwidth(self): + def test_configure_borderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'borderwidth', 0, 1.3, 2.6, 6, -2, '10p') if 'bd' in self.OPTIONS: self.checkPixelsParam(widget, 'bd', 0, 1.3, 2.6, 6, -2, '10p') - def test_compound(self): + def test_configure_compound(self): widget = self.create() self.checkEnumParam(widget, 'compound', 'bottom', 'center', 'left', 'none', 'right', 'top') - def test_cursor(self): + def test_configure_cursor(self): widget = self.create() self.checkCursorParam(widget, 'cursor') - def test_disabledforeground(self): + def test_configure_disabledforeground(self): widget = self.create() self.checkColorParam(widget, 'disabledforeground') - def test_exportselection(self): + def test_configure_exportselection(self): widget = self.create() self.checkBooleanParam(widget, 'exportselection') - def test_font(self): + def test_configure_font(self): widget = self.create() self.checkParam(widget, 'font', '-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*') self.checkInvalidParam(widget, 'font', '', errmsg='font "" doesn\'t exist') - def test_foreground(self): + def test_configure_foreground(self): widget = self.create() self.checkColorParam(widget, 'foreground') if 'fg' in self.OPTIONS: self.checkColorParam(widget, 'fg') - def test_highlightbackground(self): + def test_configure_highlightbackground(self): widget = self.create() self.checkColorParam(widget, 'highlightbackground') - def test_highlightcolor(self): + def test_configure_highlightcolor(self): widget = self.create() self.checkColorParam(widget, 'highlightcolor') - def test_highlightthickness(self): + def test_configure_highlightthickness(self): widget = self.create() self.checkPixelsParam(widget, 'highlightthickness', 0, 1.3, 2.6, 6, '10p') self.checkParam(widget, 'highlightthickness', -2, expected=0, conv=self._conv_pixels) - @unittest.skipIf(sys.platform == 'darwin', - 'crashes with Cocoa Tk (issue19733)') - def test_image(self): + def test_configure_image(self): widget = self.create() self.checkImageParam(widget, 'image') - def test_insertbackground(self): + def test_configure_insertbackground(self): widget = self.create() self.checkColorParam(widget, 'insertbackground') - def test_insertborderwidth(self): + def test_configure_insertborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'insertborderwidth', 0, 1.3, 2.6, 6, -2, '10p') - def test_insertofftime(self): + def test_configure_insertofftime(self): widget = self.create() self.checkIntegerParam(widget, 'insertofftime', 100) - def test_insertontime(self): + def test_configure_insertontime(self): widget = self.create() self.checkIntegerParam(widget, 'insertontime', 100) - def test_insertwidth(self): + def test_configure_insertwidth(self): widget = self.create() self.checkPixelsParam(widget, 'insertwidth', 1.3, 2.6, -2, '10p') - def test_jump(self): + def test_configure_jump(self): widget = self.create() self.checkBooleanParam(widget, 'jump') - def test_justify(self): + def test_configure_justify(self): widget = self.create() self.checkEnumParam(widget, 'justify', 'left', 'right', 'center', errmsg='bad justification "{}": must be ' @@ -372,154 +369,155 @@ class StandardOptionsTests: errmsg='ambiguous justification "": must be ' 'left, right, or center') - def test_orient(self): + def test_configure_orient(self): widget = self.create() self.assertEqual(str(widget['orient']), self.default_orient) self.checkEnumParam(widget, 'orient', 'horizontal', 'vertical') - def test_padx(self): + def test_configure_padx(self): widget = self.create() self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, -2, '12m', conv=self._conv_pad_pixels) - def test_pady(self): + def test_configure_pady(self): widget = self.create() self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, -2, '12m', conv=self._conv_pad_pixels) - def test_relief(self): + def test_configure_relief(self): widget = self.create() self.checkReliefParam(widget, 'relief') - def test_repeatdelay(self): + def test_configure_repeatdelay(self): widget = self.create() self.checkIntegerParam(widget, 'repeatdelay', -500, 500) - def test_repeatinterval(self): + def test_configure_repeatinterval(self): widget = self.create() self.checkIntegerParam(widget, 'repeatinterval', -500, 500) - def test_selectbackground(self): + def test_configure_selectbackground(self): widget = self.create() self.checkColorParam(widget, 'selectbackground') - def test_selectborderwidth(self): + def test_configure_selectborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p') - def test_selectforeground(self): + def test_configure_selectforeground(self): widget = self.create() self.checkColorParam(widget, 'selectforeground') - def test_setgrid(self): + def test_configure_setgrid(self): widget = self.create() self.checkBooleanParam(widget, 'setgrid') - def test_state(self): + def test_configure_state(self): widget = self.create() self.checkEnumParam(widget, 'state', 'active', 'disabled', 'normal') - def test_takefocus(self): + def test_configure_takefocus(self): widget = self.create() self.checkParams(widget, 'takefocus', '0', '1', '') - def test_text(self): + def test_configure_text(self): widget = self.create() self.checkParams(widget, 'text', '', 'any string') - def test_textvariable(self): + def test_configure_textvariable(self): widget = self.create() var = tkinter.StringVar(self.root) self.checkVariableParam(widget, 'textvariable', var) - def test_troughcolor(self): + def test_configure_troughcolor(self): widget = self.create() self.checkColorParam(widget, 'troughcolor') - def test_underline(self): + def test_configure_underline(self): widget = self.create() self.checkIntegerParam(widget, 'underline', 0, 1, 10) - def test_wraplength(self): + def test_configure_wraplength(self): widget = self.create() self.checkPixelsParam(widget, 'wraplength', 100) - def test_xscrollcommand(self): + def test_configure_xscrollcommand(self): widget = self.create() self.checkCommandParam(widget, 'xscrollcommand') - def test_yscrollcommand(self): + def test_configure_yscrollcommand(self): widget = self.create() self.checkCommandParam(widget, 'yscrollcommand') # non-standard but common options - def test_command(self): + def test_configure_command(self): widget = self.create() self.checkCommandParam(widget, 'command') - def test_indicatoron(self): + def test_configure_indicatoron(self): widget = self.create() self.checkBooleanParam(widget, 'indicatoron') - def test_offrelief(self): + def test_configure_offrelief(self): widget = self.create() self.checkReliefParam(widget, 'offrelief') - def test_overrelief(self): + def test_configure_overrelief(self): widget = self.create() self.checkReliefParam(widget, 'overrelief') - def test_selectcolor(self): + def test_configure_selectcolor(self): widget = self.create() self.checkColorParam(widget, 'selectcolor') - def test_selectimage(self): + def test_configure_selectimage(self): widget = self.create() self.checkImageParam(widget, 'selectimage') @requires_tcl(8, 5) - def test_tristateimage(self): + def test_configure_tristateimage(self): widget = self.create() self.checkImageParam(widget, 'tristateimage') @requires_tcl(8, 5) - def test_tristatevalue(self): + def test_configure_tristatevalue(self): widget = self.create() self.checkParam(widget, 'tristatevalue', 'unknowable') - def test_variable(self): + def test_configure_variable(self): widget = self.create() var = tkinter.DoubleVar(self.root) self.checkVariableParam(widget, 'variable', var) class IntegerSizeTests: - def test_height(self): + def test_configure_height(self): widget = self.create() self.checkIntegerParam(widget, 'height', 100, -100, 0) - def test_width(self): + def test_configure_width(self): widget = self.create() self.checkIntegerParam(widget, 'width', 402, -402, 0) class PixelSizeTests: - def test_height(self): + def test_configure_height(self): widget = self.create() self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '3c') - def test_width(self): + def test_configure_width(self): widget = self.create() self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i') def add_standard_options(*source_classes): - # This decorator adds test_xxx methods from source classes for every xxx - # option in the OPTIONS class attribute if they are not defined explicitly. + # This decorator adds test_configure_xxx methods from source classes for + # every xxx option in the OPTIONS class attribute if they are not defined + # explicitly. def decorator(cls): for option in cls.OPTIONS: - methodname = 'test_' + option + methodname = 'test_configure_' + option if not hasattr(cls, methodname): for source_class in source_classes: if hasattr(source_class, methodname): diff --git a/Lib/tkinter/tix.py b/Lib/tkinter/tix.py index ac545502e4..7d24075403 100644 --- a/Lib/tkinter/tix.py +++ b/Lib/tkinter/tix.py @@ -386,10 +386,8 @@ class TixWidget(tkinter.Widget): self.tk.call(name, 'configure', '-' + option, value) # These are missing from Tkinter def image_create(self, imgtype, cnf={}, master=None, **kw): - if not master: - master = tkinter._default_root - if not master: - raise RuntimeError('Too early to create image') + if master is None: + master = self if kw and cnf: cnf = _cnfmerge((cnf, kw)) elif kw: cnf = kw options = () @@ -469,16 +467,13 @@ class DisplayStyle: (multiple) Display Items""" def __init__(self, itemtype, cnf={}, *, master=None, **kw): - if not master: + if master is None: if 'refwindow' in kw: master = kw['refwindow'] elif 'refwindow' in cnf: master = cnf['refwindow'] else: - master = tkinter._default_root - if not master: - raise RuntimeError("Too early to create display style: " - "no root window") + master = tkinter._get_default_root('create display style') self.tk = master.tk self.stylename = self.tk.call('tixDisplayStyle', itemtype, *self._options(cnf,kw) ) @@ -867,7 +862,7 @@ class HList(TixWidget, XView, YView): return self.tk.call(self._w, 'add', entry, *self._options(cnf, kw)) def add_child(self, parent=None, cnf={}, **kw): - if not parent: + if parent is None: parent = '' return self.tk.call( self._w, 'addchild', parent, *self._options(cnf, kw)) diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py index c7c71cd5a5..b854235a62 100644 --- a/Lib/tkinter/ttk.py +++ b/Lib/tkinter/ttk.py @@ -81,8 +81,6 @@ def _mapdict_values(items): # ['active selected', 'grey', 'focus', [1, 2, 3, 4]] opt_val = [] for *state, val in items: - # hacks for backward compatibility - state[0] # raise IndexError if empty if len(state) == 1: # if it is empty (something that evaluates to False), then # format it to Tcl code to denote the "normal" state @@ -243,19 +241,22 @@ def _script_from_settings(settings): def _list_from_statespec(stuple): """Construct a list from the given statespec tuple according to the accepted statespec accepted by _format_mapdict.""" - nval = [] - for val in stuple: - typename = getattr(val, 'typename', None) - if typename is None: - nval.append(val) - else: # this is a Tcl object + if isinstance(stuple, str): + return stuple + result = [] + it = iter(stuple) + for state, val in zip(it, it): + if hasattr(state, 'typename'): # this is a Tcl object + state = str(state).split() + elif isinstance(state, str): + state = state.split() + elif not isinstance(state, (tuple, list)): + state = (state,) + if hasattr(val, 'typename'): val = str(val) - if typename == 'StateSpec': - val = val.split() - nval.append(val) + result.append((*state, val)) - it = iter(nval) - return [_flatten(spec) for spec in zip(it, it)] + return result def _list_from_layouttuple(tk, ltuple): """Construct a list from the tuple returned by ttk::layout, this is @@ -348,12 +349,7 @@ def setup_master(master=None): If it is not allowed to use the default root and master is None, RuntimeError is raised.""" if master is None: - if tkinter._support_default_root: - master = tkinter._default_root or tkinter.Tk() - else: - raise RuntimeError( - "No master specified and tkinter is " - "configured to not support default root") + master = tkinter._get_default_root() return master @@ -395,13 +391,12 @@ class Style(object): or something else of your preference. A statespec is compound of one or more states and then a value.""" if query_opt is not None: - return _list_from_statespec(self.tk.splitlist( - self.tk.call(self._name, "map", style, '-%s' % query_opt))) + result = self.tk.call(self._name, "map", style, '-%s' % query_opt) + return _list_from_statespec(self.tk.splitlist(result)) - return _splitdict( - self.tk, - self.tk.call(self._name, "map", style, *_format_mapdict(kw)), - conv=_tclobj_to_py) + result = self.tk.call(self._name, "map", style, *_format_mapdict(kw)) + return {k: _list_from_statespec(self.tk.splitlist(v)) + for k, v in _splitdict(self.tk, result).items()} def lookup(self, style, option, state=None, default=None): @@ -574,7 +569,7 @@ class Widget(tkinter.Widget): matches statespec. statespec is expected to be a sequence.""" ret = self.tk.getboolean( self.tk.call(self._w, "instate", ' '.join(statespec))) - if ret and callback: + if ret and callback is not None: return callback(*args, **kw) return ret @@ -1538,7 +1533,10 @@ class LabeledScale(Frame): scale_side = 'bottom' if self._label_top else 'top' label_side = 'top' if scale_side == 'bottom' else 'bottom' self.scale.pack(side=scale_side, fill='x') - tmp = Label(self).pack(side=label_side) # place holder + # Dummy required to make frame correct height + dummy = Label(self) + dummy.pack(side=label_side) + dummy.lower() self.label.place(anchor='n' if label_side == 'top' else 's') # update the label as scale or variable changes diff --git a/Lib/traceback.py b/Lib/traceback.py index a19e38718b..4e008bc0e0 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -84,7 +84,19 @@ _context_message = ( "another exception occurred:\n\n") -def print_exception(etype, value, tb, limit=None, file=None, chain=True): +_sentinel = object() + + +def _parse_value_tb(exc, value, tb): + if (value is _sentinel) != (tb is _sentinel): + raise ValueError("Both or neither of value and tb must be given") + if value is tb is _sentinel: + return exc, exc.__traceback__ + return value, tb + + +def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ + file=None, chain=True): """Print exception up to 'limit' stack trace entries from 'tb' to 'file'. This differs from print_tb() in the following ways: (1) if @@ -95,9 +107,7 @@ def print_exception(etype, value, tb, limit=None, file=None, chain=True): occurred with a caret on the next line indicating the approximate position of the error. """ - # format_exception has ignored etype for some time, and code such as cgitb - # passes in bogus values as a result. For compatibility with such code we - # ignore it here (rather than in the new TracebackException API). + value, tb = _parse_value_tb(exc, value, tb) if file is None: file = sys.stderr for line in TracebackException( @@ -105,7 +115,8 @@ def print_exception(etype, value, tb, limit=None, file=None, chain=True): print(line, file=file, end="") -def format_exception(etype, value, tb, limit=None, chain=True): +def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ + chain=True): """Format a stack trace and the exception information. The arguments have the same meaning as the corresponding arguments @@ -114,19 +125,15 @@ def format_exception(etype, value, tb, limit=None, chain=True): these lines are concatenated and printed, exactly the same text is printed as does print_exception(). """ - # format_exception has ignored etype for some time, and code such as cgitb - # passes in bogus values as a result. For compatibility with such code we - # ignore it here (rather than in the new TracebackException API). + value, tb = _parse_value_tb(exc, value, tb) return list(TracebackException( type(value), value, tb, limit=limit).format(chain=chain)) -def format_exception_only(etype, value): +def format_exception_only(exc, /, value=_sentinel): """Format the exception part of a traceback. - The arguments are the exception type and value such as given by - sys.last_type and sys.last_value. The return value is a list of - strings, each ending in a newline. + The return value is a list of strings, each ending in a newline. Normally, the list contains a single string; however, for SyntaxError exceptions, it contains several lines that (when @@ -137,7 +144,10 @@ def format_exception_only(etype, value): string in the list. """ - return list(TracebackException(etype, value, None).format_exception_only()) + if value is _sentinel: + value = exc + return list(TracebackException( + type(value), value, None).format_exception_only()) # -- not official API but folk probably use these two functions. @@ -500,7 +510,6 @@ class TracebackException: _seen=_seen) else: context = None - self.exc_traceback = exc_traceback self.__cause__ = cause self.__context__ = context self.__suppress_context__ = \ @@ -516,7 +525,8 @@ class TracebackException: if exc_type and issubclass(exc_type, SyntaxError): # Handle SyntaxError's specially self.filename = exc_value.filename - self.lineno = str(exc_value.lineno) + lno = exc_value.lineno + self.lineno = str(lno) if lno is not None else None self.text = exc_value.text self.offset = exc_value.offset self.msg = exc_value.msg @@ -575,9 +585,12 @@ class TracebackException: def _format_syntax_error(self, stype): """Format SyntaxError exceptions (internal helper).""" # Show exactly where the problem was found. - filename = self.filename or "<string>" - lineno = str(self.lineno) or '?' - yield ' File "{}", line {}\n'.format(filename, lineno) + filename_suffix = '' + if self.lineno is not None: + yield ' File "{}", line {}\n'.format( + self.filename or "<string>", self.lineno) + elif self.filename is not None: + filename_suffix = ' ({})'.format(self.filename) text = self.text if text is not None: @@ -595,7 +608,7 @@ class TracebackException: caretspace = ((c if c.isspace() else ' ') for c in ltext[:caret]) yield ' {}^\n'.format(''.join(caretspace)) msg = self.msg or "<no detail available>" - yield "{}: {}\n".format(stype, msg) + yield "{}: {}{}\n".format(stype, msg, filename_suffix) def format(self, *, chain=True): """Format the exception. @@ -617,7 +630,7 @@ class TracebackException: not self.__suppress_context__): yield from self.__context__.format(chain=chain) yield _context_message - if self.exc_traceback is not None: + if self.stack: yield 'Traceback (most recent call last):\n' - yield from self.stack.format() + yield from self.stack.format() yield from self.format_exception_only() diff --git a/Lib/tracemalloc.py b/Lib/tracemalloc.py index 69b4170ec8..cec99c5970 100644 --- a/Lib/tracemalloc.py +++ b/Lib/tracemalloc.py @@ -226,7 +226,7 @@ class Traceback(Sequence): return str(self[0]) def __repr__(self): - s = "<Traceback %r" % tuple(self) + s = f"<Traceback {tuple(self)}" if self._total_nframe is None: s += ">" else: diff --git a/Lib/typing.py b/Lib/typing.py index 3fa97a4a15..7b79876d4e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -4,8 +4,10 @@ The typing module: Support for gradual typing as defined by PEP 484. At large scale, the structure of the module is following: * Imports and exports, all public names should be explicitly added to __all__. * Internal helper functions: these should never be used in code outside this module. -* _SpecialForm and its instances (special forms): Any, NoReturn, ClassVar, Union, Optional -* Two classes whose instances can be type arguments in addition to types: ForwardRef and TypeVar +* _SpecialForm and its instances (special forms): + Any, NoReturn, ClassVar, Union, Optional, Concatenate +* Classes whose instances can be type arguments in addition to types: + ForwardRef, TypeVar and ParamSpec * The core of internal generics API: _GenericAlias and _VariadicGenericAlias, the latter is currently only used by Tuple and Callable. All subscripted types like X[int], Union[int, str], etc., are instances of either of these classes. @@ -36,11 +38,13 @@ __all__ = [ 'Any', 'Callable', 'ClassVar', + 'Concatenate', 'Final', 'ForwardRef', 'Generic', 'Literal', 'Optional', + 'ParamSpec', 'Protocol', 'Tuple', 'Type', @@ -120,6 +124,16 @@ __all__ = [ # namespace, but excluded from __all__ because they might stomp on # legitimate imports of those modules. + +def _type_convert(arg): + """For converting None to type(None), and strings to ForwardRef.""" + if arg is None: + return type(None) + if isinstance(arg, str): + return ForwardRef(arg) + return arg + + def _type_check(arg, msg, is_argument=True): """Check that the argument is a type, and return it (internal helper). @@ -136,10 +150,7 @@ def _type_check(arg, msg, is_argument=True): if is_argument: invalid_generic_forms = invalid_generic_forms + (ClassVar, Final) - if arg is None: - return type(None) - if isinstance(arg, str): - return ForwardRef(arg) + arg = _type_convert(arg) if (isinstance(arg, _GenericAlias) and arg.__origin__ in invalid_generic_forms): raise TypeError(f"{arg} is not valid as type argument") @@ -147,7 +158,7 @@ def _type_check(arg, msg, is_argument=True): return arg if isinstance(arg, _SpecialForm) or arg in (Generic, Protocol): raise TypeError(f"Plain {arg} is not valid as type argument") - if isinstance(arg, (type, TypeVar, ForwardRef, types.Union)): + if isinstance(arg, (type, TypeVar, ForwardRef, types.Union, ParamSpec)): return arg if not callable(arg): raise TypeError(f"{msg} Got {arg!r:.100}.") @@ -176,14 +187,14 @@ def _type_repr(obj): def _collect_type_vars(types): - """Collect all type variable contained in types in order of - first appearance (lexicographic order). For example:: + """Collect all type variable-like variables contained + in types in order of first appearance (lexicographic order). For example:: _collect_type_vars((T, List[S, T])) == (T, S) """ tvars = [] for t in types: - if isinstance(t, TypeVar) and t not in tvars: + if isinstance(t, _TypeVarLike) and t not in tvars: tvars.append(t) if isinstance(t, (_GenericAlias, GenericAlias)): tvars.extend([t for t in t.__parameters__ if t not in tvars]) @@ -201,6 +212,35 @@ def _check_generic(cls, parameters, elen): raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};" f" actual {alen}, expected {elen}") +def _prepare_paramspec_params(cls, params): + """Prepares the parameters for a Generic containing ParamSpec + variables (internal helper). + """ + # Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612. + if len(cls.__parameters__) == 1 and len(params) > 1: + return (params,) + else: + _params = [] + # Convert lists to tuples to help other libraries cache the results. + for p, tvar in zip(params, cls.__parameters__): + if isinstance(tvar, ParamSpec) and isinstance(p, list): + p = tuple(p) + _params.append(p) + return tuple(_params) + +def _deduplicate(params): + # Weed out strict duplicates, preserving the first of each occurrence. + all_params = set(params) + if len(all_params) < len(params): + new_params = [] + for t in params: + if t in all_params: + new_params.append(t) + all_params.remove(t) + params = new_params + assert not all_params, all_params + return params + def _remove_dups_flatten(parameters): """An internal helper for Union creation and substitution: flatten Unions @@ -215,38 +255,45 @@ def _remove_dups_flatten(parameters): params.extend(p[1:]) else: params.append(p) - # Weed out strict duplicates, preserving the first of each occurrence. - all_params = set(params) - if len(all_params) < len(params): - new_params = [] - for t in params: - if t in all_params: - new_params.append(t) - all_params.remove(t) - params = new_params - assert not all_params, all_params + + return tuple(_deduplicate(params)) + + +def _flatten_literal_params(parameters): + """An internal helper for Literal creation: flatten Literals among parameters""" + params = [] + for p in parameters: + if isinstance(p, _LiteralGenericAlias): + params.extend(p.__args__) + else: + params.append(p) return tuple(params) _cleanups = [] -def _tp_cache(func): +def _tp_cache(func=None, /, *, typed=False): """Internal wrapper caching __getitem__ of generic types with a fallback to original function for non-hashable arguments. """ - cached = functools.lru_cache()(func) - _cleanups.append(cached.cache_clear) + def decorator(func): + cached = functools.lru_cache(typed=typed)(func) + _cleanups.append(cached.cache_clear) - @functools.wraps(func) - def inner(*args, **kwds): - try: - return cached(*args, **kwds) - except TypeError: - pass # All real errors (not unhashable args) are raised below. - return func(*args, **kwds) - return inner + @functools.wraps(func) + def inner(*args, **kwds): + try: + return cached(*args, **kwds) + except TypeError: + pass # All real errors (not unhashable args) are raised below. + return func(*args, **kwds) + return inner + if func is not None: + return decorator(func) + + return decorator def _eval_type(t, globalns, localns, recursive_guard=frozenset()): """Evaluate all forward references in the given type t. @@ -319,6 +366,13 @@ class _SpecialForm(_Final, _root=True): def __getitem__(self, parameters): return self._getitem(self, parameters) + +class _LiteralSpecialForm(_SpecialForm, _root=True): + @_tp_cache(typed=True) + def __getitem__(self, parameters): + return self._getitem(self, parameters) + + @_SpecialForm def Any(self, parameters): """Special type indicating an unconstrained type. @@ -436,7 +490,7 @@ def Optional(self, parameters): arg = _type_check(parameters, f"{self} requires a single type.") return Union[arg, type(None)] -@_SpecialForm +@_LiteralSpecialForm def Literal(self, parameters): """Special typing form to define literal types (a.k.a. value types). @@ -460,7 +514,17 @@ def Literal(self, parameters): """ # There is no '_type_check' call because arguments to Literal[...] are # values, not types. - return _GenericAlias(self, parameters) + if not isinstance(parameters, tuple): + parameters = (parameters,) + + parameters = _flatten_literal_params(parameters) + + try: + parameters = tuple(p for p, _ in _deduplicate(list(_value_and_type_iter(parameters)))) + except TypeError: # unhashable parameters + pass + + return _LiteralGenericAlias(self, parameters) @_SpecialForm @@ -478,6 +542,29 @@ def TypeAlias(self, parameters): raise TypeError(f"{self} is not subscriptable") +@_SpecialForm +def Concatenate(self, parameters): + """Used in conjunction with ParamSpec and Callable to represent a higher + order function which adds, removes or transforms parameters of a Callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + if parameters == (): + raise TypeError("Cannot take a Concatenate of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + if not isinstance(parameters[-1], ParamSpec): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable.") + msg = "Concatenate[arg, ...]: each arg must be a type." + parameters = tuple(_type_check(p, msg) for p in parameters) + return _ConcatenateGenericAlias(self, parameters) + + class ForwardRef(_Final, _root=True): """Internal wrapper to hold a forward reference.""" @@ -540,8 +627,41 @@ class ForwardRef(_Final, _root=True): def __repr__(self): return f'ForwardRef({self.__forward_arg__!r})' +class _TypeVarLike: + """Mixin for TypeVar-like types (TypeVar and ParamSpec).""" + def __init__(self, bound, covariant, contravariant): + """Used to setup TypeVars and ParamSpec's bound, covariant and + contravariant attributes. + """ + if covariant and contravariant: + raise ValueError("Bivariant types are not supported.") + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + if bound: + self.__bound__ = _type_check(bound, "Bound must be a type.") + else: + self.__bound__ = None + + def __or__(self, right): + return Union[self, right] + + def __ror__(self, right): + return Union[self, right] + + def __repr__(self): + if self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __reduce__(self): + return self.__name__ + -class TypeVar(_Final, _Immutable, _root=True): +class TypeVar( _Final, _Immutable, _TypeVarLike, _root=True): """Type variable. Usage:: @@ -591,20 +711,13 @@ class TypeVar(_Final, _Immutable, _root=True): def __init__(self, name, *constraints, bound=None, covariant=False, contravariant=False): self.__name__ = name - if covariant and contravariant: - raise ValueError("Bivariant types are not supported.") - self.__covariant__ = bool(covariant) - self.__contravariant__ = bool(contravariant) + super().__init__(bound, covariant, contravariant) if constraints and bound is not None: raise TypeError("Constraints cannot be combined with bound=...") if constraints and len(constraints) == 1: raise TypeError("A single constraint is not allowed") msg = "TypeVar(name, constraint, ...): constraints must be types." self.__constraints__ = tuple(_type_check(t, msg) for t in constraints) - if bound: - self.__bound__ = _type_check(bound, "Bound must be a type.") - else: - self.__bound__ = None try: def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') # for pickling except (AttributeError, ValueError): @@ -612,23 +725,68 @@ class TypeVar(_Final, _Immutable, _root=True): if def_mod != 'typing': self.__module__ = def_mod - def __or__(self, right): - return Union[self, right] - def __ror__(self, right): - return Union[self, right] +class ParamSpec(_Final, _Immutable, _TypeVarLike, _root=True): + """Parameter specification variable. - def __repr__(self): - if self.__covariant__: - prefix = '+' - elif self.__contravariant__: - prefix = '-' - else: - prefix = '~' - return prefix + self.__name__ + Usage:: - def __reduce__(self): - return self.__name__ + P = ParamSpec('P') + + Parameter specification variables exist primarily for the benefit of static + type checkers. They are used to forward the parameter types of one + Callable to another Callable, a pattern commonly found in higher order + functions and decorators. They are only valid when used in Concatenate, or + as the first argument to Callable, or as parameters for user-defined Generics. + See class Generic for more information on generic types. An example for + annotating a decorator:: + + T = TypeVar('T') + P = ParamSpec('P') + + def add_logging(f: Callable[P, T]) -> Callable[P, T]: + '''A type-safe decorator to add logging to a function.''' + def inner(*args: P.args, **kwargs: P.kwargs) -> T: + logging.info(f'{f.__name__} was called') + return f(*args, **kwargs) + return inner + + @add_logging + def add_two(x: float, y: float) -> float: + '''Add two numbers together.''' + return x + y + + Parameter specification variables defined with covariant=True or + contravariant=True can be used to declare covariant or contravariant + generic types. These keyword arguments are valid, but their actual semantics + are yet to be decided. See PEP 612 for details. + + Parameter specification variables can be introspected. e.g.: + + P.__name__ == 'T' + P.__bound__ == None + P.__covariant__ == False + P.__contravariant__ == False + + Note that only parameter specification variables defined in global scope can + be pickled. + """ + + __slots__ = ('__name__', '__bound__', '__covariant__', '__contravariant__', + '__dict__') + + args = object() + kwargs = object() + + def __init__(self, name, bound=None, covariant=False, contravariant=False): + self.__name__ = name + super().__init__(bound, covariant, contravariant) + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing': + self.__module__ = def_mod def _is_dunder(attr): @@ -738,21 +896,26 @@ class _GenericAlias(_BaseGenericAlias, _root=True): raise TypeError(f"Cannot subscript already-subscripted {self}") if not isinstance(params, tuple): params = (params,) - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) + params = tuple(_type_convert(p) for p in params) + if any(isinstance(t, ParamSpec) for t in self.__parameters__): + params = _prepare_paramspec_params(self, params) _check_generic(self, params, len(self.__parameters__)) subst = dict(zip(self.__parameters__, params)) new_args = [] for arg in self.__args__: - if isinstance(arg, TypeVar): + if isinstance(arg, _TypeVarLike): arg = subst[arg] elif isinstance(arg, (_GenericAlias, GenericAlias)): subparams = arg.__parameters__ if subparams: subargs = tuple(subst[x] for x in subparams) arg = arg[subargs] - new_args.append(arg) + # Required to flatten out the args for CallableGenericAlias + if self.__origin__ == collections.abc.Callable and isinstance(arg, tuple): + new_args.extend(arg) + else: + new_args.append(arg) return self.copy_with(tuple(new_args)) def copy_with(self, params): @@ -839,15 +1002,18 @@ class _SpecialGenericAlias(_BaseGenericAlias, _root=True): class _CallableGenericAlias(_GenericAlias, _root=True): def __repr__(self): assert self._name == 'Callable' - if len(self.__args__) == 2 and self.__args__[0] is Ellipsis: + args = self.__args__ + if len(args) == 2 and (args[0] is Ellipsis + or isinstance(args[0], (ParamSpec, _ConcatenateGenericAlias))): return super().__repr__() return (f'typing.Callable' - f'[[{", ".join([_type_repr(a) for a in self.__args__[:-1]])}], ' - f'{_type_repr(self.__args__[-1])}]') + f'[[{", ".join([_type_repr(a) for a in args[:-1]])}], ' + f'{_type_repr(args[-1])}]') def __reduce__(self): args = self.__args__ - if not (len(args) == 2 and args[0] is ...): + if not (len(args) == 2 and (args[0] is Ellipsis + or isinstance(args[0], (ParamSpec, _ConcatenateGenericAlias)))): args = list(args[:-1]), args[-1] return operator.getitem, (Callable, args) @@ -862,13 +1028,13 @@ class _CallableType(_SpecialGenericAlias, _root=True): raise TypeError("Callable must be used as " "Callable[[arg, ...], result].") args, result = params - if args is Ellipsis: - params = (Ellipsis, result) - else: - if not isinstance(args, list): - raise TypeError(f"Callable[args, result]: args must be a list." - f" Got {args}") + # This relaxes what args can be on purpose to allow things like + # PEP 612 ParamSpec. Responsibility for whether a user is using + # Callable[...] properly is deferred to static type checkers. + if isinstance(args, list): params = (tuple(args), result) + else: + params = (args, result) return self.__getitem_inner__(params) @_tp_cache @@ -878,8 +1044,9 @@ class _CallableType(_SpecialGenericAlias, _root=True): result = _type_check(result, msg) if args is Ellipsis: return self.copy_with((_TypingEllipsis, result)) - msg = "Callable[[arg, ...], result]: each arg must be a type." - args = tuple(_type_check(arg, msg) for arg in args) + if not isinstance(args, tuple): + args = (args,) + args = tuple(_type_convert(arg) for arg in args) params = args + (result,) return self.copy_with(params) @@ -930,6 +1097,25 @@ class _UnionGenericAlias(_GenericAlias, _root=True): return True +def _value_and_type_iter(parameters): + return ((p, type(p)) for p in parameters) + + +class _LiteralGenericAlias(_GenericAlias, _root=True): + + def __eq__(self, other): + if not isinstance(other, _LiteralGenericAlias): + return NotImplemented + + return set(_value_and_type_iter(self.__args__)) == set(_value_and_type_iter(other.__args__)) + + def __hash__(self): + return hash(frozenset(_value_and_type_iter(self.__args__))) + + +class _ConcatenateGenericAlias(_GenericAlias, _root=True): + pass + class Generic: """Abstract base class for generic types. @@ -961,18 +1147,20 @@ class Generic: if not params and cls is not Tuple: raise TypeError( f"Parameter list to {cls.__qualname__}[...] cannot be empty") - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) + params = tuple(_type_convert(p) for p in params) if cls in (Generic, Protocol): # Generic and Protocol can only be subscripted with unique type variables. - if not all(isinstance(p, TypeVar) for p in params): + if not all(isinstance(p, _TypeVarLike) for p in params): raise TypeError( - f"Parameters to {cls.__name__}[...] must all be type variables") + f"Parameters to {cls.__name__}[...] must all be type variables " + f"or parameter specification variables.") if len(set(params)) != len(params): raise TypeError( f"Parameters to {cls.__name__}[...] must all be unique") else: # Subscripting a regular Generic subclass. + if any(isinstance(t, ParamSpec) for t in cls.__parameters__): + params = _prepare_paramspec_params(cls, params) _check_generic(cls, params, len(cls.__parameters__)) return _GenericAlias(cls, params) @@ -1990,14 +2178,14 @@ def TypedDict(typename, fields=None, /, *, total=True, **kwargs): raise TypeError("TypedDict takes either a dict or keyword arguments," " but not both") - ns = {'__annotations__': dict(fields), '__total__': total} + ns = {'__annotations__': dict(fields)} try: # Setting correct module is necessary to make typed dict classes pickleable. ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass - return _TypedDictMeta(typename, (), ns) + return _TypedDictMeta(typename, (), ns, total=total) _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {}) TypedDict.__mro_entries__ = lambda bases: (_TypedDict,) diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index b495a5f6cc..720f682efb 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -406,7 +406,7 @@ class NonCallableMock(Base): # Check if spec is an async object or function bound_args = _MOCK_SIG.bind_partial(cls, *args, **kw).arguments spec_arg = bound_args.get('spec_set', bound_args.get('spec')) - if spec_arg and _is_async_obj(spec_arg): + if spec_arg is not None and _is_async_obj(spec_arg): bases = (AsyncMockMixin, cls) new = type(cls.__name__, bases, {'__doc__': cls.__doc__}) instance = _safe_super(NonCallableMock, cls).__new__(new) @@ -631,9 +631,10 @@ class NonCallableMock(Base): elif _is_magic(name): raise AttributeError(name) if not self._mock_unsafe: - if name.startswith(('assert', 'assret')): - raise AttributeError("Attributes cannot start with 'assert' " - "or 'assret'") + if name.startswith(('assert', 'assret', 'asert', 'aseert', 'assrt')): + raise AttributeError( + f"{name!r} is not a valid assertion. Use a spec " + f"for the mock if {name!r} is meant to be an attribute.") result = self._mock_children.get(name) if result is _deleted: @@ -1241,6 +1242,17 @@ def _importer(target): return thing +# _check_spec_arg_typos takes kwargs from commands like patch and checks that +# they don't contain common misspellings of arguments related to autospeccing. +def _check_spec_arg_typos(kwargs_to_check): + typos = ("autospect", "auto_spec", "set_spec") + for typo in typos: + if typo in kwargs_to_check: + raise RuntimeError( + f"{typo!r} might be a typo; use unsafe=True if this is intended" + ) + + class _patch(object): attribute_name = None @@ -1248,7 +1260,7 @@ class _patch(object): def __init__( self, getter, attribute, new, spec, create, - spec_set, autospec, new_callable, kwargs + spec_set, autospec, new_callable, kwargs, *, unsafe=False ): if new_callable is not None: if new is not DEFAULT: @@ -1259,6 +1271,8 @@ class _patch(object): raise ValueError( "Cannot use 'autospec' and 'new_callable' together" ) + if not unsafe: + _check_spec_arg_typos(kwargs) self.getter = getter self.attribute = attribute @@ -1568,7 +1582,7 @@ def _get_target(target): def _patch_object( target, attribute, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, - new_callable=None, **kwargs + new_callable=None, *, unsafe=False, **kwargs ): """ patch the named member (`attribute`) on an object (`target`) with a mock @@ -1590,7 +1604,7 @@ def _patch_object( getter = lambda: target return _patch( getter, attribute, new, spec, create, - spec_set, autospec, new_callable, kwargs + spec_set, autospec, new_callable, kwargs, unsafe=unsafe ) @@ -1645,7 +1659,7 @@ def _patch_multiple(target, spec=None, create=False, spec_set=None, def patch( target, new=DEFAULT, spec=None, create=False, - spec_set=None, autospec=None, new_callable=None, **kwargs + spec_set=None, autospec=None, new_callable=None, *, unsafe=False, **kwargs ): """ `patch` acts as a function decorator, class decorator or a context @@ -1707,6 +1721,10 @@ def patch( use "as" then the patched object will be bound to the name after the "as"; very useful if `patch` is creating a mock object for you. + Patch will raise a `RuntimeError` if passed some common misspellings of + the arguments autospec and spec_set. Pass the argument `unsafe` with the + value True to disable that check. + `patch` takes arbitrary keyword arguments. These will be passed to `AsyncMock` if the patched object is asynchronous, to `MagicMock` otherwise or to `new_callable` if specified. @@ -1717,7 +1735,7 @@ def patch( getter, attribute = _get_target(target) return _patch( getter, attribute, new, spec, create, - spec_set, autospec, new_callable, kwargs + spec_set, autospec, new_callable, kwargs, unsafe=unsafe ) @@ -2567,7 +2585,7 @@ call = _Call(from_kall=False) def create_autospec(spec, spec_set=False, instance=False, _parent=None, - _name=None, **kwargs): + _name=None, *, unsafe=False, **kwargs): """Create a mock object using another object as a spec. Attributes on the mock will use the corresponding attribute on the `spec` object as their spec. @@ -2583,6 +2601,10 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, spec for an instance object by passing `instance=True`. The returned mock will only be callable if instances of the mock are callable. + `create_autospec` will raise a `RuntimeError` if passed some common + misspellings of the arguments autospec and spec_set. Pass the argument + `unsafe` with the value True to disable that check. + `create_autospec` also takes arbitrary keyword arguments that are passed to the constructor of the created mock.""" if _is_list(spec): @@ -2600,6 +2622,8 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None, _kwargs = {} if _kwargs and instance: _kwargs['_spec_as_instance'] = True + if not unsafe: + _check_spec_arg_typos(kwargs) _kwargs.update(kwargs) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index ce674e713e..e38f41e1d2 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -38,6 +38,12 @@ class Something(object): def smeth(a, b, c, d=None): pass +class Typos(): + autospect = None + auto_spec = None + set_spec = None + + def something(a): pass @@ -1598,14 +1604,23 @@ class MockTest(unittest.TestCase): #Issue21238 def test_mock_unsafe(self): m = Mock() - msg = "Attributes cannot start with 'assert' or 'assret'" + msg = "is not a valid assertion. Use a spec for the mock" with self.assertRaisesRegex(AttributeError, msg): m.assert_foo_call() with self.assertRaisesRegex(AttributeError, msg): m.assret_foo_call() + with self.assertRaisesRegex(AttributeError, msg): + m.asert_foo_call() + with self.assertRaisesRegex(AttributeError, msg): + m.aseert_foo_call() + with self.assertRaisesRegex(AttributeError, msg): + m.assrt_foo_call() m = Mock(unsafe=True) m.assert_foo_call() m.assret_foo_call() + m.asert_foo_call() + m.aseert_foo_call() + m.assrt_foo_call() #Issue21262 def test_assert_not_called(self): @@ -2156,6 +2171,62 @@ class MockTest(unittest.TestCase): obj = mock(spec=Something) self.assertIsInstance(obj, Something) + def test_bool_not_called_when_passing_spec_arg(self): + class Something: + def __init__(self): + self.obj_with_bool_func = unittest.mock.MagicMock() + + obj = Something() + with unittest.mock.patch.object(obj, 'obj_with_bool_func', autospec=True): pass + + self.assertEqual(obj.obj_with_bool_func.__bool__.call_count, 0) + + def test_misspelled_arguments(self): + class Foo(): + one = 'one' + # patch, patch.object and create_autospec need to check for misspelled + # arguments explicitly and throw a RuntimError if found. + with self.assertRaises(RuntimeError): + with patch(f'{__name__}.Something.meth', autospect=True): pass + with self.assertRaises(RuntimeError): + with patch.object(Foo, 'one', autospect=True): pass + with self.assertRaises(RuntimeError): + with patch(f'{__name__}.Something.meth', auto_spec=True): pass + with self.assertRaises(RuntimeError): + with patch.object(Foo, 'one', auto_spec=True): pass + with self.assertRaises(RuntimeError): + with patch(f'{__name__}.Something.meth', set_spec=True): pass + with self.assertRaises(RuntimeError): + with patch.object(Foo, 'one', set_spec=True): pass + with self.assertRaises(RuntimeError): + m = create_autospec(Foo, set_spec=True) + # patch.multiple, on the other hand, should flag misspelled arguments + # through an AttributeError, when trying to find the keys from kwargs + # as attributes on the target. + with self.assertRaises(AttributeError): + with patch.multiple( + f'{__name__}.Something', meth=DEFAULT, autospect=True): pass + with self.assertRaises(AttributeError): + with patch.multiple( + f'{__name__}.Something', meth=DEFAULT, auto_spec=True): pass + with self.assertRaises(AttributeError): + with patch.multiple( + f'{__name__}.Something', meth=DEFAULT, set_spec=True): pass + + with patch(f'{__name__}.Something.meth', unsafe=True, autospect=True): + pass + with patch.object(Foo, 'one', unsafe=True, autospect=True): pass + with patch(f'{__name__}.Something.meth', unsafe=True, auto_spec=True): + pass + with patch.object(Foo, 'one', unsafe=True, auto_spec=True): pass + with patch(f'{__name__}.Something.meth', unsafe=True, set_spec=True): + pass + with patch.object(Foo, 'one', unsafe=True, set_spec=True): pass + m = create_autospec(Foo, set_spec=True, unsafe=True) + with patch.multiple( + f'{__name__}.Typos', autospect=True, set_spec=True, auto_spec=True): + pass + if __name__ == '__main__': unittest.main() diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index a8c870b977..39974d975e 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -202,6 +202,8 @@ def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=cafile, capath=capath) + # send ALPN extension to indicate HTTP/1.1 protocol + context.set_alpn_protocols(['http/1.1']) https_handler = HTTPSHandler(context=context) opener = build_opener(https_handler) elif context: diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index cea91308ce..6023c1e138 100755 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -550,7 +550,7 @@ def register_standard_browsers(): cmd = "xdg-settings get default-web-browser".split() raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) result = raw_result.decode().strip() - except (FileNotFoundError, subprocess.CalledProcessError, PermissionError) : + except (FileNotFoundError, subprocess.CalledProcessError, PermissionError, NotADirectoryError) : pass else: global _os_preferred_browser diff --git a/Lib/xml/etree/ElementPath.py b/Lib/xml/etree/ElementPath.py index d318e65d84..1cbd8399d1 100644 --- a/Lib/xml/etree/ElementPath.py +++ b/Lib/xml/etree/ElementPath.py @@ -65,8 +65,9 @@ xpath_tokenizer_re = re.compile( r"//?|" r"\.\.|" r"\(\)|" + r"!=|" r"[/.*:\[\]\(\)@=])|" - r"((?:\{[^}]+\})?[^/\[\]\(\)@=\s]+)|" + r"((?:\{[^}]+\})?[^/\[\]\(\)@!=\s]+)|" r"\s+" ) @@ -253,15 +254,19 @@ def prepare_predicate(next, token): if elem.get(key) is not None: yield elem return select - if signature == "@-='": - # [@attribute='value'] + if signature == "@-='" or signature == "@-!='": + # [@attribute='value'] or [@attribute!='value'] key = predicate[1] value = predicate[-1] def select(context, result): for elem in result: if elem.get(key) == value: yield elem - return select + def select_negated(context, result): + for elem in result: + if (attr_value := elem.get(key)) is not None and attr_value != value: + yield elem + return select_negated if '!=' in signature else select if signature == "-" and not re.match(r"\-?\d+$", predicate[0]): # [tag] tag = predicate[0] @@ -270,8 +275,10 @@ def prepare_predicate(next, token): if elem.find(tag) is not None: yield elem return select - if signature == ".='" or (signature == "-='" and not re.match(r"\-?\d+$", predicate[0])): - # [.='value'] or [tag='value'] + if signature == ".='" or signature == ".!='" or ( + (signature == "-='" or signature == "-!='") + and not re.match(r"\-?\d+$", predicate[0])): + # [.='value'] or [tag='value'] or [.!='value'] or [tag!='value'] tag = predicate[0] value = predicate[-1] if tag: @@ -281,12 +288,22 @@ def prepare_predicate(next, token): if "".join(e.itertext()) == value: yield elem break + def select_negated(context, result): + for elem in result: + for e in elem.iterfind(tag): + if "".join(e.itertext()) != value: + yield elem + break else: def select(context, result): for elem in result: if "".join(elem.itertext()) == value: yield elem - return select + def select_negated(context, result): + for elem in result: + if "".join(elem.itertext()) != value: + yield elem + return select_negated if '!=' in signature else select if signature == "-" or signature == "-()" or signature == "-()-": # [index] or [last()] or [last()-index] if signature == "-": diff --git a/Lib/zipfile.py b/Lib/zipfile.py index e1a50a3eb5..0eed4ce9a6 100644 --- a/Lib/zipfile.py +++ b/Lib/zipfile.py @@ -2379,8 +2379,8 @@ class Path: def __repr__(self): return self.__repr.format(self=self) - def joinpath(self, add): - next = posixpath.join(self.at, add) + def joinpath(self, *other): + next = posixpath.join(self.at, *other) return self._next(self.root.resolve_dir(next)) __truediv__ = joinpath diff --git a/Lib/zipimport.py b/Lib/zipimport.py index 080e0c4d98..ce3e00e24f 100644 --- a/Lib/zipimport.py +++ b/Lib/zipimport.py @@ -22,6 +22,7 @@ import _io # for open import marshal # for loads import sys # for modules import time # for mktime +import _warnings # For warn() __all__ = ['ZipImportError', 'zipimporter'] @@ -42,7 +43,7 @@ END_CENTRAL_DIR_SIZE = 22 STRING_END_ARCHIVE = b'PK\x05\x06' MAX_COMMENT_LEN = (1 << 16) - 1 -class zipimporter: +class zipimporter(_bootstrap_external._LoaderBasics): """zipimporter(archivepath) -> zipimporter object Create a new zipimporter instance. 'archivepath' must be a path to @@ -115,6 +116,8 @@ class zipimporter: full path name if it's possibly a portion of a namespace package, or None otherwise. The optional 'path' argument is ignored -- it's there for compatibility with the importer protocol. + + Deprecated since Python 3.10. Use find_spec() instead. """ mi = _get_module_info(self, fullname) if mi is not None: @@ -146,15 +149,43 @@ class zipimporter: instance itself if the module was found, or None if it wasn't. The optional 'path' argument is ignored -- it's there for compatibility with the importer protocol. + + Deprecated since Python 3.10. Use find_spec() instead. """ return self.find_loader(fullname, path)[0] + def find_spec(self, fullname, target=None): + """Create a ModuleSpec for the specified module. + + Returns None if the module cannot be found. + """ + module_info = _get_module_info(self, fullname) + if module_info is not None: + return _bootstrap.spec_from_loader(fullname, self, is_package=module_info) + else: + # Not a module or regular package. See if this is a directory, and + # therefore possibly a portion of a namespace package. + + # We're only interested in the last path component of fullname + # earlier components are recorded in self.prefix. + modpath = _get_module_path(self, fullname) + if _is_dir(self, modpath): + # This is possibly a portion of a namespace + # package. Return the string representing its path, + # without a trailing separator. + path = f'{self.archive}{path_sep}{modpath}' + spec = _bootstrap.ModuleSpec(name=fullname, loader=None, + is_package=True) + spec.submodule_search_locations.append(path) + return spec + else: + return None def get_code(self, fullname): """get_code(fullname) -> code object. Return the code object for the specified module. Raise ZipImportError - if the module couldn't be found. + if the module couldn't be imported. """ code, ispackage, modpath = _get_module_code(self, fullname) return code @@ -184,7 +215,8 @@ class zipimporter: def get_filename(self, fullname): """get_filename(fullname) -> filename string. - Return the filename for the specified module. + Return the filename for the specified module or raise ZipImportError + if it couldn't be imported. """ # Deciding the filename requires working out where the code # would come from if the module was actually loaded @@ -236,8 +268,13 @@ class zipimporter: Load the module specified by 'fullname'. 'fullname' must be the fully qualified (dotted) module name. It returns the imported - module, or raises ZipImportError if it wasn't found. + module, or raises ZipImportError if it could not be imported. + + Deprecated since Python 3.10. Use exec_module() instead. """ + msg = ("zipimport.zipimporter.load_module() is deprecated and slated for " + "removal in Python 3.12; use exec_module() instead") + _warnings.warn(msg, DeprecationWarning) code, ispackage, modpath = _get_module_code(self, fullname) mod = sys.modules.get(fullname) if mod is None or not isinstance(mod, _module_type): @@ -577,20 +614,15 @@ def _eq_mtime(t1, t2): # Given the contents of a .py[co] file, unmarshal the data -# and return the code object. Return None if it the magic word doesn't -# match, or if the recorded .py[co] metadata does not match the source, -# (we do this instead of raising an exception as we fall back -# to .py if available and we don't want to mask other errors). +# and return the code object. Raises ImportError it the magic word doesn't +# match, or if the recorded .py[co] metadata does not match the source. def _unmarshal_code(self, pathname, fullpath, fullname, data): exc_details = { 'name': fullname, 'path': fullpath, } - try: - flags = _bootstrap_external._classify_pyc(data, fullname, exc_details) - except ImportError: - return None + flags = _bootstrap_external._classify_pyc(data, fullname, exc_details) hash_based = flags & 0b1 != 0 if hash_based: @@ -604,11 +636,8 @@ def _unmarshal_code(self, pathname, fullpath, fullname, data): source_bytes, ) - try: - _bootstrap_external._validate_hash_pyc( - data, source_hash, fullname, exc_details) - except ImportError: - return None + _bootstrap_external._validate_hash_pyc( + data, source_hash, fullname, exc_details) else: source_mtime, source_size = \ _get_mtime_and_size_of_source(self, fullpath) @@ -694,6 +723,7 @@ def _get_pyc_source(self, path): # 'fullname'. def _get_module_code(self, fullname): path = _get_module_path(self, fullname) + import_error = None for suffix, isbytecode, ispackage in _zip_searchorder: fullpath = path + suffix _bootstrap._verbose_message('trying {}{}{}', self.archive, path_sep, fullpath, verbosity=2) @@ -704,8 +734,12 @@ def _get_module_code(self, fullname): else: modpath = toc_entry[0] data = _get_data(self.archive, toc_entry) + code = None if isbytecode: - code = _unmarshal_code(self, modpath, fullpath, fullname, data) + try: + code = _unmarshal_code(self, modpath, fullpath, fullname, data) + except ImportError as exc: + import_error = exc else: code = _compile_source(modpath, data) if code is None: @@ -715,4 +749,8 @@ def _get_module_code(self, fullname): modpath = toc_entry[0] return code, ispackage, modpath else: - raise ZipImportError(f"can't find module {fullname!r}", name=fullname) + if import_error: + msg = f"module load failed: {import_error}" + raise ZipImportError(msg, name=fullname) from import_error + else: + raise ZipImportError(f"can't find module {fullname!r}", name=fullname) |