: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.
--- /dev/null
+"""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
--- /dev/null
+"""Run tests in isolated subprocesses (the test.support.isolation.runInSubprocess decorator).
+
+A failure, error or skip that happens in the subprocess is replayed in the
+parent process so that the test runner records it. The original (subprocess)
+traceback is attached as the cause of the replayed exception, the same way
+:mod:`concurrent.futures` surfaces tracebacks from worker processes.
+"""
+
+import functools
+import os
+import sys
+import unittest
+
+# Let unittest strip this module's frames from tracebacks, so only the original
+# subprocess traceback (attached as the cause) is shown, not the replay frames.
+__unittest = True
+
+# test.support globals set by regrtest (libregrtest/setup.py) that affect how
+# tests run and which are skipped at runtime in the subprocess.
+_PROPAGATED_CONFIG = (
+ 'use_resources', # -u (is_resource_enabled/requires)
+ 'max_memuse', 'real_max_memuse', # -M (bigmemtest)
+ 'verbose', # -v
+ 'failfast', # -f
+)
+
+def _child_config():
+ import test.support as support
+ return {name: getattr(support, name) for name in _PROPAGATED_CONFIG}
+
+def _apply_child_config(config):
+ """Set up the child to run the test like a regrtest worker would.
+
+ Mark this process as the subprocess, mirror the parent's -u/-M/-v config,
+ then suppress the Windows CRT assertion dialogs, which would block a debug
+ build on a modal dialog and hang the parent.
+ """
+ global runningInSubprocess
+ import marshal
+ import test.support as support
+ runningInSubprocess = True
+ for name, value in marshal.loads(bytes.fromhex(config)).items():
+ setattr(support, name, value)
+ support.suppress_msvcrt_asserts(support.verbose >= 2)
+
+# True inside the subprocess spawned by @runInSubprocess(), set by
+# _apply_child_config() before the test is imported. Fixtures can test it to
+# decide what to run in the subprocess as opposed to the parent process.
+runningInSubprocess = False
+
+
+class _RemoteTraceback(Exception):
+ """Carry a formatted traceback string from the subprocess for display.
+
+ Attached as the ``__cause__`` of the replayed failure/error, so that the
+ original traceback is shown by the traceback machinery.
+ """
+ def __init__(self, tb):
+ self.tb = tb
+
+ def __str__(self):
+ return self.tb
+
+
+class _SubprocessTestError(Exception):
+ """Replay a subprocess error (as opposed to a failure) in the parent."""
+
+
+def _decode(data):
+ # Decode the child output, which is only ever shown as a diagnostic: an
+ # undecodable byte must not hide the failure it is part of.
+ if not data:
+ return ''
+ import locale
+ encoding = 'utf-8' if sys.flags.utf8_mode else locale.getencoding()
+ return data.decode(encoding, 'backslashreplace').replace('\r\n', '\n')
+
+
+def _remote(detail):
+ # Wrap the subprocess traceback the way concurrent.futures does, so it is
+ # clearly delimited when shown as the cause.
+ return _RemoteTraceback(f'\n"""\n{detail}"""')
+
+
+def _check_subprocess_support():
+ # runInSubprocess() always runs the test in a subprocess, so skip (in the
+ # parent) on platforms that do not support spawning one.
+ import test.support as support
+ if not support.has_subprocess_support:
+ raise unittest.SkipTest('requires subprocess support')
+
+
+def _run_in_subprocess(module, qualname):
+ """Run module.qualname (a test method or class) in a fresh subprocess.
+
+ Return ``(payload, output, returncode)``, where *payload* is the decoded
+ ``{'outcomes': ..., 'durations': ...}`` mapping from the subprocess, or
+ ``None`` if it did not run to completion (crash, import error, ...).
+ """
+ import marshal
+ import subprocess
+ import tempfile
+ fd, result_path = tempfile.mkstemp(suffix='.json')
+ os.close(fd)
+ try:
+ # Pass the config on the command line, not in the environment, so that
+ # the test cannot pass it on to the processes it spawns itself. Use
+ # marshal, not json: it is built in, so the child imports nothing that
+ # the test would not see in a normal test run.
+ cmd = [sys.executable, '-m', 'test.support.subprocess_runner',
+ module, qualname, result_path,
+ marshal.dumps(_child_config()).hex()]
+ proc = subprocess.run(cmd, capture_output=True)
+ try:
+ with open(result_path, 'rb') as f:
+ payload = marshal.load(f)
+ except (OSError, EOFError, ValueError):
+ payload = None
+ finally:
+ try:
+ os.unlink(result_path)
+ except OSError:
+ pass
+ return payload, _decode(proc.stdout) + _decode(proc.stderr), proc.returncode
+
+
+def _replay_outcome(test, outcome):
+ kind = outcome['kind']
+ detail = outcome['detail']
+ if kind == 'skipped':
+ test.skipTest(detail) # the detail is the skip reason, not a traceback
+ elif kind in ('failure', 'expected_failure'):
+ # Replay an expected failure like a failure: the wrapper keeps the
+ # @expectedFailure marker (via functools.wraps), so the parent records
+ # the raised exception as an expectedFailure.
+ exc = test.failureException('test failed in the subprocess')
+ raise exc from _remote(detail)
+ else: # 'error'
+ exc = _SubprocessTestError('test failed in the subprocess')
+ raise exc from _remote(detail)
+
+
+def _replay_outcomes(test, outcomes):
+ # Replay each subtest outcome in its own subTest() context so that they are
+ # reported individually, then replay the whole-test outcome (if any).
+ main = []
+ for outcome in outcomes:
+ if outcome['subtest']:
+ with test.subTest(outcome['desc']):
+ _replay_outcome(test, outcome)
+ else:
+ main.append(outcome)
+ for outcome in main:
+ _replay_outcome(test, outcome)
+
+
+def _raise_fixture_outcome(outcome):
+ # Reproduce a setUpClass()/setUpModule() failure or skip from the
+ # subprocess in a parent-process fixture, so it applies to every test.
+ if outcome['kind'] == 'skipped':
+ raise unittest.SkipTest(outcome['detail'])
+ exc = _SubprocessTestError('class failed in the subprocess')
+ raise exc from _remote(outcome['detail'])
+
+
+def _isolate_method(func):
+ @functools.wraps(func)
+ def wrapper(self, /, *args, **kwargs):
+ if runningInSubprocess:
+ # Already running in the subprocess: run the real test.
+ return func(self, *args, **kwargs)
+ _check_subprocess_support()
+ cls = type(self)
+ qualname = f'{cls.__qualname__}.{func.__name__}'
+ payload, output, returncode = _run_in_subprocess(cls.__module__,
+ qualname)
+ if payload is None:
+ exc = _SubprocessTestError(
+ f'test did not complete in a subprocess (exit code {returncode})')
+ raise exc from _remote(output)
+ # The parent measures this method's own duration (the real cost of the
+ # isolated run, subprocess startup included), so nothing to forward here.
+ _replay_outcomes(self, payload['outcomes'])
+ return wrapper
+
+
+def _isolate_class(cls):
+ # Unwrap to the plain functions so the replacements can call them with the
+ # runtime cls; a bound classmethod would freeze the decoration-time class
+ # and a subclass would run the fixtures bound to the base class.
+ orig_setUpClass = cls.setUpClass.__func__
+ orig_tearDownClass = cls.tearDownClass.__func__
+ # Hook the _call*() indirections rather than setUp(), tearDown() and the
+ # test methods themselves, to cover what a subclass adds or overrides too.
+ orig_callSetUp = cls._callSetUp
+ orig_callTearDown = cls._callTearDown
+ orig_callTestMethod = cls._callTestMethod
+ orig_addDuration = cls._addDuration
+
+ def setUpClass(cls):
+ if runningInSubprocess:
+ orig_setUpClass(cls)
+ return
+ _check_subprocess_support()
+ # Run the whole class in a single subprocess and stash the outcomes
+ # for the test methods to replay.
+ payload, output, returncode = _run_in_subprocess(cls.__module__,
+ cls.__qualname__)
+ if payload is None:
+ exc = _SubprocessTestError(
+ f'class did not complete in a subprocess (exit code {returncode})')
+ raise exc from _remote(output)
+ by_id = {}
+ for outcome in payload['outcomes']:
+ if outcome['fixture']:
+ # A setUpClass()/setUpModule() failure or skip: apply it to the
+ # whole class by raising it here, in the parent's setUpClass().
+ _raise_fixture_outcome(outcome)
+ by_id.setdefault(outcome['id'], []).append(outcome)
+ cls._isolated_outcomes = by_id
+ cls._isolated_durations = dict(payload.get('durations', ()))
+
+ def tearDownClass(cls):
+ if runningInSubprocess:
+ orig_tearDownClass(cls)
+ else:
+ cls._isolated_outcomes = None
+ cls._isolated_durations = None
+
+ def _callSetUp(self):
+ # In the parent the real test does not run, so neither should setUp().
+ if runningInSubprocess:
+ orig_callSetUp(self)
+
+ def _callTearDown(self):
+ if runningInSubprocess:
+ orig_callTearDown(self)
+
+ def _callTestMethod(self, method):
+ if runningInSubprocess:
+ orig_callTestMethod(self, method)
+ return
+ by_id = getattr(type(self), '_isolated_outcomes', None)
+ if by_id is None:
+ raise _SubprocessTestError(
+ f'{type(self).__name__} did not run in a subprocess; '
+ f'an overriding setUpClass() must call super().setUpClass()')
+ _replay_outcomes(self, by_id.get(self.id(), []))
+
+ def _addDuration(self, result, elapsed):
+ # In the parent, report the subprocess timing rather than the (instant)
+ # replay time; subprocess startup is paid once, in setUpClass.
+ if not runningInSubprocess:
+ durations = getattr(type(self), '_isolated_durations', None) or {}
+ elapsed = durations.get(self.id(), elapsed)
+ orig_addDuration(self, result, elapsed)
+
+ cls.setUpClass = classmethod(setUpClass)
+ cls.tearDownClass = classmethod(tearDownClass)
+ cls._callSetUp = _callSetUp
+ cls._callTearDown = _callTearDown
+ cls._callTestMethod = _callTestMethod
+ cls._addDuration = _addDuration
+ return cls
+
+
+def runInSubprocess():
+ """Decorator to run a test method or class in a fresh subprocess.
+
+ The decorated test runs in a separate, fresh Python process, so it does not
+ share global or interpreter state with the rest of the test run. When a
+ :class:`~unittest.TestCase` subclass is decorated, the whole class runs in a
+ single subprocess and its ``setUpClass()``/``setUpModule()`` fixtures run
+ once there; when a method is decorated, only that method runs in a
+ subprocess. Decorated methods must take no extra arguments.
+
+ A failure, error or skip of the whole test is reported for the test, and
+ individual subtests (:meth:`~unittest.TestCase.subTest`) that fail or are
+ skipped are reported individually. The original subprocess traceback is
+ shown as the cause of a reported failure or error. Use
+ :data:`runningInSubprocess` in fixtures to choose what to run in the subprocess.
+
+ The test is skipped on platforms without subprocess support, since it must
+ spawn one.
+ """
+ def decorator(obj):
+ if isinstance(obj, type) and issubclass(obj, unittest.TestCase):
+ return _isolate_class(obj)
+ return _isolate_method(obj)
+ return decorator
--- /dev/null
+"""Run a single test method in this (sub)process and report the result.
+
+Invoked as ``python -m test.support.subprocess_runner MODULE QUALNAME OUTFILE
+CONFIG`` by :func:`test.support.isolation.runInSubprocess`. CONFIG is the
+marshalled test.support configuration of the parent test run, as a hex string.
+The outcome of the test (including that of each individual subtest) is
+marshalled to OUTFILE. This module is not meant to be imported.
+
+Import as little as possible before running the test: every module imported
+here is state that the test would not see in a normal test run.
+"""
+
+import marshal
+import sys
+import unittest
+from unittest.case import _SubTest
+
+if __name__ != '__main__':
+ raise ImportError('this module cannot be directly imported')
+
+if len(sys.argv) != 5:
+ print('usage: python -m test.support.subprocess_runner '
+ 'MODULE QUALNAME OUTFILE CONFIG', file=sys.stderr)
+ sys.exit(2)
+
+module, qualname, outfile, config = sys.argv[1:]
+
+# Set up the child before importing the test.
+from test.support.isolation import _apply_child_config
+_apply_child_config(config)
+
+
+class _Result(unittest.TestResult):
+ # Capture per-test durations keyed by test id, so the parent can report the
+ # subprocess timings instead of its own replay time.
+ def __init__(self):
+ super().__init__()
+ self.id_durations = []
+
+ def addDuration(self, test, elapsed):
+ super().addDuration(test, elapsed)
+ self.id_durations.append((test.id(), elapsed))
+
+
+# Resolve the qualname in the imported module, rather than letting
+# loadTestsFromName() guess where the module name ends: it guesses by trying
+# imports that fail, and a failing import pulls in importlib.resources.
+__import__(module)
+suite = unittest.TestLoader().loadTestsFromName(qualname, sys.modules[module])
+result = _Result()
+suite.run(result)
+
+
+def _outcome(kind, test, detail):
+ subtest = isinstance(test, _SubTest)
+ real = test.test_case if subtest else test
+ return {
+ 'kind': kind,
+ 'subtest': subtest,
+ 'desc': test._subDescription() if subtest else '',
+ # id() groups outcomes by test method; a non-TestCase (e.g. an
+ # _ErrorHolder) marks a setUpClass()/setUpModule() fixture failure.
+ 'id': real.id(),
+ 'fixture': not isinstance(real, unittest.TestCase),
+ 'detail': detail,
+ }
+
+
+outcomes = [_outcome('failure', t, tb) for t, tb in result.failures]
+outcomes += [_outcome('error', t, tb) for t, tb in result.errors]
+outcomes += [_outcome('expected_failure', t, tb)
+ for t, tb in result.expectedFailures]
+outcomes += [_outcome('skipped', t, reason) for t, reason in result.skipped]
+
+payload = {'outcomes': outcomes, 'durations': result.id_durations}
+with open(outfile, 'wb') as f:
+ marshal.dump(payload, f)
+
+sys.exit(0)
import warnings
from test import support
+from test.support import isolation
from test.support import hashlib_helper
from test.support import import_helper
from test.support import os_helper
self.assertIsInstance(h, self._hashlib.HASH)
+class TestIsolated(unittest.TestCase):
+ # Drive the sample tests in test._isolated_sample (which really spawn
+ # subprocesses through @isolation.runInSubprocess()) under a private
+ # TestResult, and check that each subprocess outcome is replayed in the parent.
+
+ @staticmethod
+ def _run(name):
+ suite = unittest.TestLoader().loadTestsFromName(
+ 'test._isolated_sample.' + name)
+ result = unittest.TestResult()
+ suite.run(result)
+ return result
+
+ @staticmethod
+ def _names(items):
+ # Map outcome entries (which are (test, detail) pairs, except
+ # unexpectedSuccesses which are bare tests) to their method names.
+ names = []
+ for item in items:
+ test = item[0] if isinstance(item, tuple) else item
+ names.append(test.id().rpartition('.')[2])
+ return sorted(names)
+
+ @support.requires_subprocess()
+ def test_method_outcomes(self):
+ result = self._run('MethodSample')
+ self.assertEqual(result.testsRun, 6)
+ self.assertEqual(self._names(result.failures), ['test_fail'])
+ self.assertEqual(self._names(result.errors), ['test_error'])
+ self.assertEqual(self._names(result.skipped), ['test_skip'])
+ self.assertEqual(self._names(result.expectedFailures),
+ ['test_expected_failure'])
+ self.assertEqual(self._names(result.unexpectedSuccesses),
+ ['test_unexpected_success'])
+
+ @support.requires_subprocess()
+ def test_class_outcomes(self):
+ result = self._run('ClassSample')
+ self.assertEqual(result.testsRun, 3)
+ self.assertEqual(self._names(result.failures), ['test_fail'])
+ self.assertEqual(self._names(result.expectedFailures),
+ ['test_expected_failure'])
+ self.assertEqual(result.errors, [])
+ self.assertEqual(result.unexpectedSuccesses, [])
+
+ @support.requires_subprocess()
+ def test_subtests_reported_individually(self):
+ result = self._run('SubtestSample')
+ self.assertEqual(result.testsRun, 1)
+ self.assertEqual(len(result.failures), 1)
+ test, _ = result.failures[0]
+ self.assertIn('i=1', str(test))
+
+ @support.requires_subprocess()
+ def test_skip_reason_propagated(self):
+ result = self._run('MethodSample.test_skip')
+ self.assertEqual([reason for _, reason in result.skipped], ['nope'])
+
+ @support.requires_subprocess()
+ def test_subprocess_traceback_is_cause(self):
+ result = self._run('MethodSample.test_fail')
+ self.assertEqual(len(result.failures), 1)
+ _, tb = result.failures[0]
+ # The real assertion that failed in the subprocess is shown ...
+ self.assertIn('self.assertEqual(1, 2)', tb)
+ # ... as the direct cause of the replayed failure ...
+ self.assertIn('direct cause', tb)
+ # ... without leaking the parent-side replay frames.
+ self.assertNotIn('isolation.py', tb)
+
+ @support.requires_subprocess()
+ def test_durations_forwarded_for_class(self):
+ from test._isolated_sample import DURATION_SLEEP
+ result = unittest.TestResult()
+ suite = unittest.TestLoader().loadTestsFromName(
+ 'test._isolated_sample.DurationSample')
+ suite.run(result)
+ # The duration reported in the parent is the one measured in the
+ # subprocess (around the sleep), not the near-instant replay time.
+ self.assertEqual(len(result.collectedDurations), 1)
+ name, elapsed = result.collectedDurations[0]
+ self.assertEqual(name.split()[0], 'test_slow')
+ self.assertGreaterEqual(elapsed, DURATION_SLEEP / 2)
+
+ @support.requires_subprocess()
+ def test_subclass_of_isolated_class(self):
+ # Both samples pass only if the fixtures are bound to the runtime class
+ # and what the subclass adds or overrides runs in the subprocess.
+ for name, count in (('SubclassingSample', 1), ('SubclassSample', 2)):
+ with self.subTest(sample=name):
+ result = self._run(name)
+ self.assertEqual(result.testsRun, count)
+ self.assertEqual(result.failures, [])
+ self.assertEqual(result.errors, [])
+
+ @support.requires_subprocess()
+ def test_subclass_bypassing_setupclass_is_reported(self):
+ # A class that never ran in a subprocess must error out, not pass with
+ # no outcome to replay.
+ result = self._run('BrokenSubclassSample')
+ self.assertEqual(result.testsRun, 1)
+ self.assertEqual(len(result.errors), 1)
+ self.assertIn('did not run in a subprocess', result.errors[0][1])
+
+ def test_skipped_without_subprocess_support(self):
+ # On a platform without subprocess support the test is skipped in the
+ # parent, before any subprocess is spawned.
+ calls = []
+ orig = isolation._run_in_subprocess
+ with support.swap_attr(support, 'has_subprocess_support', False):
+ isolation._run_in_subprocess = lambda *a, **k: calls.append(a)
+ try:
+ result = self._run('MethodSample.test_pass')
+ finally:
+ isolation._run_in_subprocess = orig
+ self.assertEqual(result.testsRun, 1)
+ self.assertEqual(len(result.skipped), 1)
+ self.assertEqual(calls, [])
+
+
if __name__ == '__main__':
unittest.main()
--- /dev/null
+Add the :func:`test.support.isolation.runInSubprocess` decorator
+to run a test method or ``TestCase`` subclass in a fresh interpreter subprocess,
+isolated from the rest of the test run.