Skip to main content

pytest-isolated

Tests PyPI Python Version Downloads License Platform

A cross-platform pytest plugin that runs marked tests in isolated subprocesses with intelligent grouping.

Why pytest-isolated?

Ever had a test that passes alone but fails in the suite? Or tests that mysteriously hang on CI? pytest-isolated solves this by running tests in completely isolated subprocesses.

Common problems it solves:

  • ✅ Segfaults and crashes don't kill your entire test suite
  • ✅ Tests modifying global state don't affect each other
  • ✅ Hanging tests timeout without blocking other tests
  • ✅ C extension crashes are contained
  • ✅ Resource leaks are isolated per test

Cheatsheet for pytest-forked users

This plugin is inspired by pytest-forked. See pytest-forked migration guide for a quick reference comparing features.

Quick Start

1. Install:

pip install pytest-isolated

2. Mark a flaky test:

# Example 1: Simple isolation
@pytest.mark.isolated
def test_with_clean_state():
    import os
    os.environ["DEBUG"] = "true"
    # Other tests won't see this change

# Example 2: Crash protection
@pytest.mark.isolated
def test_that_crashes():
    import ctypes
    ctypes.string_at(0)  # Crash is contained!

3. Run pytest normally:

pytest  # Crash is isolated, suite continues

Features

  • Run tests in fresh Python subprocesses to prevent state pollution
  • Group related tests to run together in the same subprocess
  • Handles crashes, timeouts, and setup/teardown failures
  • Respects pytest's standard output capture settings (-s, --capture)
  • Works with pytest reporters (JUnit XML, etc.)
  • Configurable timeouts to prevent hanging subprocesses
  • Cross-platform: Linux, macOS, Windows

Basic Usage

Mark tests to run in isolated subprocesses:

import pytest

@pytest.mark.isolated
def test_isolated():
    # Runs in a fresh subprocess
    assert True

Tests with the same group run together in one subprocess:

# Using keyword argument
@pytest.mark.isolated(group="mygroup")
def test_one():
    shared_state.append(1)

@pytest.mark.isolated(group="mygroup")
def test_two():
    # Sees state from test_one
    assert len(shared_state) == 2

# Or using positional argument
@pytest.mark.isolated("mygroup")
def test_three():
    shared_state.append(3)

Set timeout per test group:

@pytest.mark.isolated(timeout=30)
def test_with_timeout():
    # This group gets 30 second timeout (overrides global setting)
    expensive_operation()

Note: Tests without an explicit group parameter each run in their own unique subprocess for maximum isolation.

Class and Module Markers

Apply to entire classes to share state between methods:

@pytest.mark.isolated
class TestDatabase:
    def test_setup(self):
        self.db = create_database()

    def test_query(self):
        # Shares state with test_setup
        result = self.db.query("SELECT 1")
        assert result

Apply to entire modules using pytestmark:

import pytest

pytestmark = pytest.mark.isolated

def test_one():
    # Runs in isolated subprocess
    pass

def test_two():
    # Shares subprocess with test_one
    pass

Marker Precedence

When @pytest.mark.isolated appears at multiple scopes, the closest marker wins (following pytest's standard get_closest_marker convention):

  1. Explicit group always wins. @pytest.mark.isolated(group="name") uses that group regardless of scope.
  2. Function > class > module. A function-level @pytest.mark.isolated breaks out of any class or module group into its own subprocess.
  3. Class > module. A class marker groups its methods together, even inside a module with pytestmark.

Configuration

Command Line

# Run all tests in isolation (even without @pytest.mark.isolated)
pytest --isolated

# Set isolated test timeout (seconds)
pytest --isolated-timeout=60

# Disable subprocess isolation for debugging
pytest --no-isolation

# Control output capture (standard pytest flags work with isolated tests)
pytest -s                    # Disable capture, show all output
pytest --capture=sys         # Capture at sys.stdout/stderr level

# Combine with pytest debugger
pytest --no-isolation --pdb

pytest.ini / pyproject.toml

[pytest]
isolated_timeout = 300

Or in pyproject.toml:

[tool.pytest.ini_options]
isolated_timeout = "300"

Real-World Use Cases

🌍 Isolating Global State

Prevent environment variable and configuration changes from leaking between tests:

@pytest.mark.isolated
def test_modifies_environ():
    import os
    os.environ["MY_VAR"] = "value"
    # Won't affect other tests

@pytest.mark.isolated
def test_clean_environ():
    import os
    assert "MY_VAR" not in os.environ  # Fresh environment

🔄 Testing Singletons

Group tests that share singleton state, or isolate them completely:

@pytest.mark.isolated(group="singleton_tests")
def test_singleton_init():
    from myapp import DatabaseConnection
    db = DatabaseConnection.get_instance()
    assert db is not None

@pytest.mark.isolated(group="singleton_tests")
def test_singleton_reuse():
    db = DatabaseConnection.get_instance()
    # Same instance as previous test in group

⚙️ Testing Process Resources

Safely modify signal handlers and other process-level settings:

@pytest.mark.isolated
def test_signal_handlers():
    import signal
    signal.signal(signal.SIGTERM, custom_handler)
    # Won't interfere with pytest or other tests

💾 Testing Database Migrations

Group expensive operations while maintaining isolation from other tests:

@pytest.mark.isolated(group="database")
class TestDatabase:
    def test_migration(self):
        db.migrate()  # Expensive operation, runs once

    def test_query(self):
        # Reuses same DB from test_migration
        result = db.query("SELECT 1")
        assert result == [(1,)]

🔧 Testing C Extensions

Isolate tests that might crash from C code:

@pytest.mark.isolated
def test_numpy_operation():
    import numpy as np
    # If this segfaults, other tests still run
    result = np.array([1, 2, 3])
    assert len(result) == 3

Output and Reporting

Failed tests automatically capture and display stdout/stderr:

@pytest.mark.isolated
def test_failing():
    print("Debug info")
    assert False

Works with standard pytest reporters:

pytest --junitxml=report.xml --durations=10

Limitations

Fixtures: Module/session fixtures run in each subprocess group. Cannot share fixture objects between parent and subprocess.

Debugging: Use --no-isolation to run all tests in the main process for easier debugging with pdb or IDE debuggers.

Performance: Subprocess creation adds ~100-500ms per group. Group related tests to minimize overhead. Only mark tests that need isolation.

CLI Option Compatibility

pytest-isolated uses a forward-by-default (blacklist) model:

  • Some options are blocked with a clear UsageError when isolation is active.
  • Some options are supported but handled in the parent process (not forwarded).
  • All remaining options (including custom plugin options) are forwarded to children.

See Incompatible options reference for the full list and workarounds.

Advanced

Coverage Integration

To collect coverage from isolated tests, enable subprocess tracking in pyproject.toml:

[tool.coverage.run]
parallel = true
concurrency = ["subprocess"]

See the coverage.py subprocess documentation for details.

Timeout Handling

pytest --isolated-timeout=30

Timeout errors are clearly reported with the group name and timeout duration.

Crash Detection

If a subprocess crashes, tests in that group are marked as failed with exit code information.

Subprocess Detection

import os

if os.environ.get("PYTEST_RUNNING_IN_SUBPROCESS") == "1":
    # Running in subprocess
    pass

Troubleshooting

Tests timing out: Increase timeout with --isolated-timeout=600

Missing output: Use -s or --capture=no to see output from passing tests, or -v for verbose output. pytest-isolated respects pytest's standard capture settings.

Subprocess crashes: Check for segfaults, OOM, or signal issues. Run with -v for details.

Contributing

  1. Install pre-commit: pip install pre-commit && pre-commit install
  2. Run tests: pytest tests/ -v
  3. Open an issue before submitting PRs for new features

License

MIT License - see LICENSE file for details.

Metadata

Release files for pytest-isolated 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-isolated 0.5.0
File Size Uploaded
pytest_isolated-0.5.0.tar.gz 36.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-isolated 0.5.0
File Interpreter ABI Platform
pytest_isolated-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 56.8 kB

Release files / pytest_isolated-0.5.0.tar.gz

Download URL pytest_isolated-0.5.0.tar.gz
Size 36.9 kB
Tags Source
SHA-256 checksum
How to use checksums
e7bcb91fa93d32ec7dfd7cb662617f9343538c4d62424d57fdd3f904ab92238d
BLAKE2b-256 checksum
How to use checksums
f7a8add62c9d5d7b6fd51aa9888ba28c445e67071b68a8677d769b10a18aaa12
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release files / pytest_isolated-0.5.0-py3-none-any.whl

Download URL pytest_isolated-0.5.0-py3-none-any.whl
Size 19.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
009575ed652288a489eef20835393b674b5f674035d63ea1ec38f97c282d3431
BLAKE2b-256 checksum
How to use checksums
8dc56cb2b8246d6ea2ebe50353580c490c53703e214df6f060c9c9cb806f483a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.13

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.4.4

1 release file

0.4.3

1 release file

0.4.2

1 release file

0.4.1

1 release file

0.4.0

1 release file

0.3.0

1 release file

0.2.0

1 release file

0.1.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page