Running a test in a fresh interpreter: `test.support.isolation.runInSubprocess()`

Some tests cannot share an interpreter with the rest of the suite: they install a signal handler or an audit hook, change the locale, enable tracing. We have handled these by running a source string through script_helper.assert_python_ok(), which costs us the test: one opaque pass/fail, no individual assertions, -m cannot select a single case, and a failure comes back as a blob of stderr.

test.support.isolation.runInSubprocess() runs a test method — or a whole TestCase — in a fresh interpreter and replays the outcome in the parent, so it stays an ordinary unittest test.

from test.support import isolation

class SignalTests(unittest.TestCase):

    @isolation.runInSubprocess()
    def test_wakeup_fd(self):
        signal.set_wakeup_fd(w)
        ...

Decorating a class runs it in a single subprocess, with setUpClass(), setUp(), tearDown() and tearDownClass() running there.

Failures, errors, skips, individual subTest() outcomes and @expectedFailure are all reported for the test they belong to, with the original subprocess traceback attached as the cause. The subprocess inherits the parent’s -u, -M, -v and -f, so requires_resource() and bigmemtest() behave the same on both sides.

A fixture can check isolation.runningInSubprocess to see which side it is on. It is true only in the child, and is set before the test module is imported — so an expensive skip probe can stay in the parent and never spawn a process at all.

Merged for 3.16 and backported to 3.15, 3.14 and 3.13, so a converted test can be backported too.

For now this is test-suite infrastructure, not public API: test.support is where it can change freely while we find out what it needs. Once it has proven itself on CPython’s own tests, the intent is to add the feature to unittest, where it belongs — so treat the current spelling and behaviour as provisional.

In flight

  • GH-155163 adds keyword-only options=, env= and timeout=, for tests needing particular interpreter flags or environment variables.
  • Example conversions: GH-152565 (test_audit), GH-152570 (test_eintr), GH-152639 (test_signal).

When not to use it

  • the contract is a crash or an abort;
  • the contract is what the test runner prints;
  • the test needs several processes, or an interpreter started differently;
  • the test is about startup, shutdown or finalization ordering;
  • the code has to run at a module top level.

If you have a test that spawns an interpreter only to get a clean one, it is probably a candidate. I am most interested in the ones that do not fit — those shape what comes next.

Issue: gh-152548 · Implementation: GH-152551 · Documentation.

9 Likes