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
9 changes: 7 additions & 2 deletions Doc/c-api/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ accessible to C code. They all work with the current interpreter thread's
This function is safe to call before :c:func:`Py_Initialize`. When called
after runtime initialization, existing audit hooks are notified and may
silently abort the operation by raising an error subclassed from
:class:`Exception` (other errors will not be silenced).
:class:`RuntimeError` (other errors will not be silenced).

The hook function is always called with an :term:`attached thread state` by
the Python interpreter that raised the event.
Expand All @@ -447,7 +447,7 @@ accessible to C code. They all work with the current interpreter thread's

If the interpreter is initialized, this function raises an auditing event
``sys.addaudithook`` with no arguments. If any existing hooks raise an
exception derived from :class:`Exception`, the new hook will not be
exception derived from :class:`RuntimeError`, the new hook will not be
added and the exception is cleared. As a result, callers cannot assume
that their hook has been added unless they control all existing hooks.

Expand All @@ -462,6 +462,11 @@ accessible to C code. They all work with the current interpreter thread's

.. versionadded:: 3.8

.. versionchanged:: 3.8.1

Exceptions derived from :class:`Exception` but not :class:`RuntimeError`
are no longer suppressed.


.. _processcontrol:

Expand Down
2 changes: 2 additions & 0 deletions Doc/library/curses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ Initialization and termination
and return the previously current screen.
Returns ``None`` if the previous screen was the one created by
:func:`initscr`.
Raises :exc:`error` if *screen* has no terminal,
as is the case for a screen returned by :func:`new_prescr`.

.. versionadded:: next

Expand Down
53 changes: 53 additions & 0 deletions Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,59 @@ The :mod:`!test.support` module defines the following functions:
:mod:`tracemalloc` is enabled.


.. currentmodule:: test.support.isolation

.. decorator:: runInSubprocess()

Decorator that runs the decorated test in a fresh interpreter subprocess, in
isolation, so that it does not share global or interpreter state with the
rest of the test run. It can decorate a test method or a whole
:class:`~unittest.TestCase` subclass. Decorated methods must take no extra
arguments. A failure, error or skip in the subprocess is reported for the
corresponding test, and individual :meth:`subtests
<unittest.TestCase.subTest>` that fail or are skipped are reported
individually. A reported failure or error shows the original subprocess
traceback as the cause of the exception.

When a **method** is decorated, only that method runs in a subprocess; all
fixtures (:meth:`~unittest.TestCase.setUp` / :meth:`~unittest.TestCase.tearDown`,
:meth:`~unittest.TestCase.setUpClass` / :meth:`~unittest.TestCase.tearDownClass`
and ``setUpModule()`` / ``tearDownModule()``) run both in the parent process
(as usual) and in the subprocess around the method.

When a **class** is decorated, the whole class runs in a single subprocess,
and :meth:`~unittest.TestCase.setUpClass`,
:meth:`~unittest.TestCase.tearDownClass`, :meth:`~unittest.TestCase.setUp`
and :meth:`~unittest.TestCase.tearDown` run once each in the subprocess and
are skipped in the parent process. A failure or skip of
:meth:`~unittest.TestCase.setUpClass` in the subprocess is reported for the
whole class. ``setUpModule()`` cannot be controlled by a class decorator,
so it still runs in the parent process too; test it with
:data:`runningInSubprocess` if needed.

The subprocess inherits the enabled resources (``-u``), memory limit
(``-M``) and verbosity (``-v``) of the parent test run, so that
:func:`~test.support.requires_resource`, :func:`~test.support.requires`,
:func:`~test.support.bigmemtest` and the like behave consistently in both
processes.

The test is skipped on platforms without subprocess support.


.. data:: runningInSubprocess

``True`` while the code runs in the isolated subprocess spawned by
:func:`runInSubprocess`, and ``False`` otherwise (including in the parent
process and in a normal, non-isolated test run). Fixtures such as
:meth:`~unittest.TestCase.setUp`, :meth:`~unittest.TestCase.tearDown`,
:meth:`~unittest.TestCase.setUpClass`, :meth:`~unittest.TestCase.tearDownClass`,
``setUpModule()`` and ``tearDownModule()`` can test it to choose which code
to run in the subprocess.


.. currentmodule:: test.support


.. function:: check_free_after_iterating(test, iter, cls, args=())

Assert instances of *cls* are deallocated after iterating.
Expand Down
5 changes: 4 additions & 1 deletion Lib/importlib/abc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Abstract base classes related to import."""
from . import _bootstrap_external
from . import machinery

try:
import _frozen_importlib
except ImportError as exc:
Expand All @@ -11,10 +12,12 @@
import _frozen_importlib_external
except ImportError:
_frozen_importlib_external = _bootstrap_external
from ._abc import Loader
import abc


# Public API
from ._abc import Loader

__all__ = [
'Loader', 'MetaPathFinder', 'PathEntryFinder',
'ResourceLoader', 'InspectLoader', 'ExecutionLoader',
Expand Down
6 changes: 4 additions & 2 deletions Lib/importlib/machinery.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""The machinery of importlib: finders, loaders, hooks, etc."""

lazy import warnings


# Public API
from ._bootstrap import ModuleSpec
from ._bootstrap import BuiltinImporter
from ._bootstrap import FrozenImporter
Expand Down Expand Up @@ -33,8 +37,6 @@ def all_suffixes():


def __getattr__(name):
import warnings

if name == 'DEBUG_BYTECODE_SUFFIXES':
warnings.warn('importlib.machinery.DEBUG_BYTECODE_SUFFIXES is '
'deprecated; use importlib.machinery.BYTECODE_SUFFIXES '
Expand Down
16 changes: 10 additions & 6 deletions Lib/importlib/util.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
"""Utility code for constructing importers, etc."""

from ._bootstrap import _resolve_name
from ._bootstrap import _find_spec

import _imp
import sys
import types


# Public API
from ._abc import Loader
from ._bootstrap import module_from_spec
from ._bootstrap import _resolve_name
from ._bootstrap import spec_from_loader
from ._bootstrap import _find_spec
from ._bootstrap_external import MAGIC_NUMBER
from ._bootstrap_external import cache_from_source
from ._bootstrap_external import decode_source
from ._bootstrap_external import source_from_cache
from ._bootstrap_external import spec_from_file_location

import _imp
import sys
import types


def source_hash(source_bytes):
"Return the hash of *source_bytes* as used in hash-based pyc files."
Expand Down
9 changes: 9 additions & 0 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3158,6 +3158,15 @@ def postloop(self):
if self.quitting:
self.detach()

@contextmanager
def _maybe_use_pyrepl_as_stdin(self):
# The server reads every command from the client over the socket, never
# from a local pyrepl. The base implementation swaps in pyrepl as stdin
# and blanks `self.prompt` to '' (pyrepl would draw the prompt itself),
# which here would transmit an empty prompt to the client whenever the
# target process happens to be pyrepl-capable. Keep the real prompt.
yield

def detach(self):
# Detach the debugger and close the socket without raising BdbQuit
self.quitting = False
Expand Down
111 changes: 111 additions & 0 deletions Lib/test/_isolated_sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""Sample tests driven by test.test_support.TestIsolated.

This module is imported, never run as a test file, so that
:func:`test.support.isolation.runInSubprocess` has a real, importable target to run in
a subprocess. Several of these tests fail, error or are skipped on purpose.
"""

import time
import unittest
from test.support import isolation

# DurationSample sleeps this long in the subprocess; a parent-reported duration
# close to it proves the subprocess timing was forwarded, not the replay time.
DURATION_SLEEP = 0.2


class MethodSample(unittest.TestCase):

@isolation.runInSubprocess()
def test_pass(self):
self.assertTrue(isolation.runningInSubprocess)

@isolation.runInSubprocess()
def test_fail(self):
self.assertEqual(1, 2)

@isolation.runInSubprocess()
def test_error(self):
raise RuntimeError('boom')

@isolation.runInSubprocess()
def test_skip(self):
self.skipTest('nope')

@isolation.runInSubprocess()
@unittest.expectedFailure
def test_expected_failure(self):
self.assertEqual(1, 2)

@isolation.runInSubprocess()
@unittest.expectedFailure
def test_unexpected_success(self):
pass


@isolation.runInSubprocess()
class ClassSample(unittest.TestCase):

def test_pass(self):
self.assertTrue(isolation.runningInSubprocess)

def test_fail(self):
self.assertEqual(1, 2)

@unittest.expectedFailure
def test_expected_failure(self):
self.assertEqual(1, 2)


class SubtestSample(unittest.TestCase):

@isolation.runInSubprocess()
def test_subtests(self):
for i in range(3):
with self.subTest(i=i):
self.assertNotEqual(i, 1)


@isolation.runInSubprocess()
class DurationSample(unittest.TestCase):

def test_slow(self):
time.sleep(DURATION_SLEEP)


@isolation.runInSubprocess()
class SubclassingSample(unittest.TestCase):
# setUpClass must run bound to the runtime class, so a subclass sees its own
# name here rather than the base class's.

@classmethod
def setUpClass(cls):
cls.setup_class_name = cls.__name__

def setUp(self):
self.set_up = True

def test_runtime_class(self):
self.assertEqual(self.setup_class_name, type(self).__name__)


class SubclassSample(SubclassingSample):
# What a subclass adds or overrides must run in the subprocess too.

def setUp(self):
super().setUp()
self.set_up_in_subclass = True

def test_added_in_subclass(self):
self.assertTrue(isolation.runningInSubprocess)
self.assertTrue(self.set_up)
self.assertTrue(self.set_up_in_subclass)


class BrokenSubclassSample(SubclassingSample):
# An overriding setUpClass() that does not call super() bypasses the
# subprocess entirely.

@classmethod
def setUpClass(cls):
pass
10 changes: 10 additions & 0 deletions Lib/test/audit-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ def test_block_add_hook_baseexception():
pass


def test_block_add_hook_valueerror():
# Non-RuntimeError exceptions (like ValueError) should propagate out
with assertRaises(ValueError):
with TestHook(
raise_on_events="sys.addaudithook", exc_type=ValueError
) as hook1:
with TestHook() as hook2:
pass


def test_marshal():
import marshal
o = ("a", "b", "c", 1, 2, 3)
Expand Down
Loading
Loading