Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions .github/workflows/reusable-check-html-ids.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,6 @@ jobs:
with:
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha }}
- name: 'Downgrade Git'
# Temporarily downgrade to 2.43 until 2.55 is in the runner image,
# to avoid "fatal: shallow file has changed since we read it" bug.
# See https://github.com/python/cpython/issues/151365.
run: |
sudo apt-get install -y --allow-downgrades 'git=1:2.43.*' 'git-man=1:2.43.*'
git --version
- name: 'Find merge base'
id: merge-base
run: |
Expand Down
9 changes: 0 additions & 9 deletions .github/workflows/reusable-context.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,6 @@ jobs:
|| ''
}}
- name: 'Downgrade Git'
# Temporarily downgrade to 2.43 until 2.55 is in the runner image,
# to avoid "fatal: shallow file has changed since we read it" bug.
# See https://github.com/python/cpython/issues/151365.
if: github.event_name == 'pull_request'
run: |
sudo apt-get install -y --allow-downgrades 'git=1:2.43.*' 'git-man=1:2.43.*'
git --version
# Adapted from https://github.com/actions/checkout/issues/520#issuecomment-1167205721
- name: Fetch commits to get branch diff
if: github.event_name == 'pull_request'
Expand Down
8 changes: 0 additions & 8 deletions .github/workflows/reusable-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,6 @@ jobs:
&& github.event.pull_request.head.sha
|| ''
}}
- name: 'Downgrade Git'
# Temporarily downgrade to 2.43 until 2.55 is in the runner image,
# to avoid "fatal: shallow file has changed since we read it" bug.
# See https://github.com/python/cpython/issues/151365.
if: github.event_name == 'pull_request'
run: |
sudo apt-get install -y --allow-downgrades 'git=1:2.43.*' 'git-man=1:2.43.*'
git --version
# Adapted from https://github.com/actions/checkout/issues/520#issuecomment-1167205721
- name: 'Fetch commits to get branch diff'
if: github.event_name == 'pull_request'
Expand Down
6 changes: 6 additions & 0 deletions Doc/library/doctest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,12 @@ from text files and modules with doctests:
.. versionchanged:: 3.15
Run each example as a :ref:`subtest <subtests>`.

.. versionchanged:: next
Report every example, as in verbose mode, if the test runner reports
more than the test names, i.e. its
:attr:`~unittest.TestResult.verbosity` is 3 or higher (for example
with ``python -m unittest -vv``).

Under the covers, :func:`DocTestSuite` creates a :class:`unittest.TestSuite` out
of :class:`!doctest.DocTestCase` instances, and :class:`!DocTestCase` is a
subclass of :class:`unittest.TestCase`. :class:`!DocTestCase` isn't documented
Expand Down
14 changes: 14 additions & 0 deletions Doc/library/imaplib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,20 @@ An :class:`IMAP4` instance has the following methods:

The following attributes are defined on instances of :class:`IMAP4`:

.. attribute:: IMAP4.capabilities

A tuple of the capabilities advertised by the server, in upper case.

It is set when the connection is established,
and refreshed after a successful :meth:`~IMAP4.login`,
:meth:`~IMAP4.authenticate` or :meth:`~IMAP4.starttls`,
because the server can advertise different capabilities
in different connection states.

.. versionchanged:: 3.14.7
Refreshed after :meth:`~IMAP4.login` and :meth:`~IMAP4.authenticate`.


.. attribute:: IMAP4.PROTOCOL_VERSION

The most recent supported protocol in the ``CAPABILITY`` response from the
Expand Down
7 changes: 4 additions & 3 deletions Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,10 @@ The :mod:`!test.support` module defines the following constants:

.. data:: verbose

``True`` when verbose output is enabled. Should be checked when more
detailed information is desired about a running test. *verbose* is set by
:mod:`test.regrtest`.
How verbose the output is: the number of :option:`!-v` options which
:mod:`test.regrtest` was run with, and therefore ``0`` when verbose output
is not enabled. Should be checked when more detailed information is
desired about a running test.


.. data:: is_jython
Expand Down
18 changes: 17 additions & 1 deletion Doc/library/unittest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ You can run tests with more detail (higher verbosity) by passing in the -v flag:

python -m unittest -v test_module

Repeat it for even more detail: ``-vv`` reports also the individual examples
of a :mod:`doctest`.

When executed without arguments :ref:`unittest-test-discovery` is started::

python -m unittest
Expand Down Expand Up @@ -291,7 +294,11 @@ The ``discover`` sub-command has the following options:

.. option:: -v, --verbose

Verbose output
Verbose output. May be repeated: ``-vv`` reports also the individual
examples of a :mod:`doctest`.

.. versionchanged:: next
The option can be repeated.

.. option:: -s, --start-directory directory

Expand Down Expand Up @@ -2149,6 +2156,15 @@ Loading and running tests

.. versionadded:: 3.5

.. attribute:: verbosity

The level of details which the test runner reports: ``0`` -- quiet,
``1`` -- progress dots, ``2`` -- test names, ``3`` -- also the
individual examples of a :mod:`doctest`. A test runner is expected to
set it to its own verbosity.

.. versionadded:: next

.. method:: wasSuccessful()

Return ``True`` if all tests run so far have passed, otherwise returns
Expand Down
41 changes: 31 additions & 10 deletions Lib/doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,18 @@ def _find_lineno(self, obj, source_lines):
## 5. DocTest Runner
######################################################################

def _make_output_function(stream):
"""Return a function writing to *stream*, whatever it can encode."""
encoding = getattr(stream, 'encoding', None)
if encoding is None or encoding.lower() == 'utf-8':
return stream.write
def out(s):
# Use backslashreplace error handling on write
s = str(s.encode(encoding, 'backslashreplace'), encoding)
stream.write(s)
return out


class DocTestRunner:
"""
A class used to run DocTest test cases, and accumulate statistics.
Expand Down Expand Up @@ -1561,14 +1573,7 @@ def run(self, test, compileflags=None, out=None, clear_globs=True):

save_stdout = sys.stdout
if out is None:
encoding = save_stdout.encoding
if encoding is None or encoding.lower() == 'utf-8':
out = save_stdout.write
else:
# Use backslashreplace error handling on write
def out(s):
s = str(s.encode(encoding, 'backslashreplace'), encoding)
save_stdout.write(s)
out = _make_output_function(save_stdout)
sys.stdout = self._fakeout

# Patch pdb.set_trace to restore sys.stdout during interactive
Expand Down Expand Up @@ -2322,6 +2327,9 @@ def report_skip(self, out, test, example):
unittest.case._addSkip(self._test_result, self._subTest(), '')

def report_success(self, out, test, example, got):
# Report "ok" if verbose, to close what report_start() opened. A
# failed or skipped example is reported by the test result instead.
super().report_success(out, test, example, got)
self._test_result.addSubTest(self._test_case, self._subTest(), None)

def report_unexpected_exception(self, out, test, example, exc_info):
Expand Down Expand Up @@ -2401,10 +2409,23 @@ def runTest(self):
if getattr(result, 'failfast', False):
optionflags |= FAIL_FAST

# Report every example only if the test runner is asked for more than
# the test names it reports at verbosity 2. Write them to its stream,
# so that they are not swallowed by result.buffer.
verbose = getattr(result, 'verbosity', 1) >= 3
stream = getattr(result, 'stream', None)
out = None
if verbose and stream is not None:
out = _make_output_function(stream)
if test.examples and not getattr(result, '_newline', True):
# End the line which startTest() left open.
out('\n')
result._newline = True

runner = _DocTestCaseRunner(optionflags=optionflags,
checker=self._dt_checker, verbose=False,
checker=self._dt_checker, verbose=verbose,
test_case=self, test_result=result)
results = runner.run(test, clear_globs=False)
results = runner.run(test, out=out, clear_globs=False)
if results.skipped == results.attempted:
raise unittest.SkipTest("all examples were skipped")

Expand Down
11 changes: 8 additions & 3 deletions Lib/imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,8 @@ def _connect(self):
self._encoding, 'replace')
raise self.error('invalid greeting: ' + greeting)

self._refresh_capabilities()
# The greeting is not a response to a command.
self._refresh_capabilities(consume=True)
if __debug__:
if self.debug >= 3:
self._mesg('CAPABILITIES: %r' % (self.capabilities,))
Expand Down Expand Up @@ -1509,10 +1510,14 @@ def _get_capabilities(self):
self.capabilities = tuple(dat.split())


def _refresh_capabilities(self):
def _refresh_capabilities(self, consume=False):
# Use a CAPABILITY response sent by the server, or ask for it.
# Unless it is consumed, the response can still be read with
# response('CAPABILITY').
if 'CAPABILITY' in self.untagged_responses:
dat = self.untagged_responses.pop('CAPABILITY')[-1]
dat = self.untagged_responses['CAPABILITY'][-1]
if consume:
del self.untagged_responses['CAPABILITY']
self.capabilities = tuple(str(dat, self._encoding).upper().split())
else:
self._get_capabilities()
Expand Down
4 changes: 4 additions & 0 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -3464,6 +3464,10 @@ def _main():
import argparse
import importlib

# The printed text can contain characters unencodable in the encoding
# of stdout, e.g. undecodable bytes of a file name.
sys.stdout.reconfigure(errors='backslashreplace')

parser = argparse.ArgumentParser()
parser.add_argument(
'object',
Expand Down
66 changes: 49 additions & 17 deletions Lib/multiprocessing/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,16 @@ def __enter__(self):
def __exit__(self, exc_type, exc_val, exc_tb):
self.terminate()

def _chain_context(exc, context):
'Set context as the context of exc, avoiding a cycle.'
seen = {id(context)}
while exc is not None and id(exc) not in seen:
seen.add(id(exc))
if exc.__context__ is None:
exc.__context__ = context
return
exc = exc.__context__

#
# Class whose instances are returned by `Pool.apply_async()`
#
Expand Down Expand Up @@ -800,13 +810,25 @@ def get(self, timeout=None):

def _set(self, i, obj):
self._success, self._value = obj
if self._callback and self._success:
self._callback(self._value)
if self._error_callback and not self._success:
self._error_callback(self._value)
self._event.set()
del self._cache[self._job]
self._pool = None
try:
if self._success:
if self._callback:
self._callback(self._value)
else:
if self._error_callback:
self._error_callback(self._value)
except BaseException as exc:
# A failed callback becomes the result of the job. If it
# propagated, it would kill the result handler thread.
if not self._success:
# do not lose the original error
_chain_context(exc, self._value)
self._success = False
self._value = exc
finally:
self._event.set()
del self._cache[self._job]
self._pool = None

__class_getitem__ = classmethod(types.GenericAlias)

Expand Down Expand Up @@ -837,23 +859,33 @@ def _set(self, i, success_result):
if success and self._success:
self._value[i*self._chunksize:(i+1)*self._chunksize] = result
if self._number_left == 0:
if self._callback:
self._callback(self._value)
del self._cache[self._job]
self._event.set()
self._pool = None
try:
if self._callback:
self._callback(self._value)
except BaseException as exc:
self._success = False
self._value = exc
finally:
del self._cache[self._job]
self._event.set()
self._pool = None
else:
if not success and self._success:
# only store first exception
self._success = False
self._value = result
if self._number_left == 0:
# only consider the result ready once all jobs are done
if self._error_callback:
self._error_callback(self._value)
del self._cache[self._job]
self._event.set()
self._pool = None
try:
if self._error_callback:
self._error_callback(self._value)
except BaseException as exc:
_chain_context(exc, self._value)
self._value = exc
finally:
del self._cache[self._job]
self._event.set()
self._pool = None

#
# Class whose instances are returned by `Pool.imap()`
Expand Down
Loading
Loading