Skip to content
Open
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
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:: isolated()

Decorator that runs the decorated test in isolation, in a fresh interpreter
subprocess, 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:`running_isolated` 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:: running_isolated

``True`` while the code runs in the isolated subprocess spawned by
:func:`isolated`, 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
74 changes: 74 additions & 0 deletions Lib/test/_isolated_sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Sample tests driven by test.test_support.TestIsolated.

This module is imported, never run as a test file, so that
:func:`test.support.isolation.isolated` 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

# A test in DurationSample sleeps this long in the subprocess; the parent
# replays it instantly, so a parent-reported duration close to this proves the
# subprocess timing was forwarded rather than the replay time measured.
DURATION_SLEEP = 0.2


class MethodSample(unittest.TestCase):

@isolation.isolated()
def test_pass(self):
self.assertTrue(isolation.running_isolated)

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

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

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

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

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


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

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

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

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


class SubtestSample(unittest.TestCase):

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


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

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