CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pytest-xdist

pytest xdist plugin for distributed testing, most importantly across multiple CPUs

Pending
Overview
Eval results
Files

distribution-scheduling.mddocs/

Distribution Scheduling

Multiple scheduling algorithms for distributing tests across workers, each optimized for different test suite characteristics and performance requirements.

Capabilities

Scheduling Protocol

All schedulers implement the Scheduling protocol, providing a consistent interface for test distribution.

class Scheduling(Protocol):
    """Protocol for all test distribution schedulers."""
    
    @property
    def nodes(self) -> list[WorkerController]:
        """List of active worker nodes."""
    
    @property
    def collection_is_completed(self) -> bool:
        """True if test collection is complete on all nodes."""
    
    @property
    def tests_finished(self) -> bool:
        """True if all tests have been executed."""
    
    @property
    def has_pending(self) -> bool:
        """True if there are pending tests to be scheduled."""
    
    def add_node(self, node: WorkerController) -> None:
        """Add a worker node to the scheduler."""
    
    def add_node_collection(
        self,
        node: WorkerController,
        collection: Sequence[str],
    ) -> None:
        """Add collected test items from a worker node."""
    
    def mark_test_complete(
        self,
        node: WorkerController,
        item_index: int,
        duration: float = 0,
    ) -> None:
        """Mark a test as completed by a worker."""
    
    def mark_test_pending(self, item: str) -> None:
        """Mark a test as pending (needs to be rescheduled)."""
    
    def remove_pending_tests_from_node(
        self,
        node: WorkerController,
        indices: Sequence[int],
    ) -> None:
        """Remove pending tests from a specific worker node."""
    
    def remove_node(self, node: WorkerController) -> str | None:
        """Remove a worker node from the scheduler."""
    
    def schedule(self) -> None:
        """Execute the scheduling algorithm to distribute tests."""

Load Balancing Scheduler

Distributes tests dynamically to available workers for optimal load balancing.

class LoadScheduling:
    """
    Load balance by sending any pending test to any available worker.
    
    Tests are distributed one at a time to the next available worker,
    providing good load balancing for test suites with varying execution times.
    """

Characteristics:

  • Dynamic distribution to available workers
  • Good for mixed execution times
  • Minimal worker idle time
  • Default scheduling mode with -n

Each Scheduler

Sends each test to all available workers (typically for testing across environments).

class EachScheduling:
    """
    Send each test to all available workers.
    
    Every test is executed on every worker, useful for testing
    across different environments or configurations.
    """

Characteristics:

  • Every test runs on every worker
  • Useful for cross-platform/environment testing
  • Significantly increases total execution time
  • Good for validation across configurations

Load Scope Scheduler

Groups tests by scope (class, module, etc.) for better fixture reuse.

class LoadScopeScheduling:
    """
    Load balance by sending pending groups of tests in the same scope
    to any available worker.
    
    Groups tests by scope (typically class or module) to improve
    fixture reuse and reduce setup/teardown overhead.
    """

Characteristics:

  • Groups tests by pytest scope
  • Improves fixture reuse efficiency
  • Better for test suites with expensive fixtures
  • Supports --loadscope-reorder for optimization

Load File Scheduler

Groups tests by file for locality and fixture sharing.

class LoadFileScheduling:
    """
    Load balance by sending tests grouped by file to any available worker.
    
    All tests from the same file are sent to the same worker,
    optimizing for file-level fixtures and reducing I/O overhead.
    """

Characteristics:

  • Groups tests by source file
  • Optimizes file-level fixture usage
  • Reduces context switching
  • Good for file-heavy test suites

Load Group Scheduler

Like load balancing but respects xdist_group markers for test grouping.

class LoadGroupScheduling:
    """
    Like LoadScheduling, but sends tests marked with 'xdist_group' 
    to the same worker.
    
    Tests marked with the same xdist_group name will always run
    on the same worker, useful for tests that share state or resources.
    """

Characteristics:

  • Respects @pytest.mark.xdist_group markers
  • Tests with same group name run on same worker
  • Allows controlled test grouping
  • Good for tests with shared state requirements

Work Stealing Scheduler

Splits tests initially, then rebalances when workers become idle.

class WorkStealingScheduling:
    """
    Split the test suite evenly between available workers initially,
    then rebalance when any worker runs out of tests.
    
    Provides good initial distribution with dynamic rebalancing
    for optimal resource utilization.
    """

Characteristics:

  • Initial even distribution
  • Dynamic rebalancing when workers finish
  • Good for large test suites
  • Minimizes communication overhead

Scheduler Factory

Hook for creating custom schedulers.

def pytest_xdist_make_scheduler(
    config: pytest.Config, log: Producer
) -> Scheduling | None:
    """
    Hook for creating custom scheduler implementations.
    
    Args:
        config: pytest configuration object
        log: logging producer for scheduler messages
        
    Returns:
        Custom scheduler instance or None to use default
    """

Usage Examples

Command Line Usage

# Load balancing (default with -n)
pytest -n 4

# Explicit load balancing
pytest -n 4 --dist load

# Each test on all workers
pytest -n 4 --dist each

# Group by scope
pytest -n 4 --dist loadscope

# Group by file
pytest -n 4 --dist loadfile

# Respect xdist_group markers
pytest -n 4 --dist loadgroup

# Work stealing
pytest -n 4 --dist worksteal

# Control loadscope reordering
pytest -n 4 --dist loadscope --loadscope-reorder
pytest -n 4 --dist loadscope --no-loadscope-reorder

Using xdist_group Marker

import pytest

# Tests with same group run on same worker
@pytest.mark.xdist_group(name="database")
def test_db_setup():
    # Database initialization
    pass

@pytest.mark.xdist_group(name="database")  
def test_db_query():
    # Can rely on setup from test_db_setup
    pass

@pytest.mark.xdist_group(name="filesystem")
def test_file_operations():
    # Different group, may run on different worker
    pass

Custom Scheduler Implementation

# In conftest.py
from xdist.scheduler.protocol import Scheduling
from xdist.workermanage import WorkerController

class CustomScheduler:
    """Example custom scheduler implementation."""
    
    def __init__(self, config, log):
        self.config = config
        self.log = log
        self._nodes = []
        self._pending = []
        self._collection_complete = False
    
    @property
    def nodes(self) -> list[WorkerController]:
        return self._nodes
    
    @property 
    def collection_is_completed(self) -> bool:
        return self._collection_complete
    
    def schedule(self) -> None:
        # Custom scheduling logic
        if self.has_pending and self.nodes:
            # Implement custom distribution algorithm
            pass

def pytest_xdist_make_scheduler(config, log):
    if config.getoption("--custom-scheduler"):
        return CustomScheduler(config, log)
    return None  # Use default scheduler

Scheduler Selection Logic

# How pytest-xdist selects schedulers based on --dist option
SCHEDULER_MAP = {
    'each': EachScheduling,
    'load': LoadScheduling, 
    'loadscope': LoadScopeScheduling,
    'loadfile': LoadFileScheduling,
    'loadgroup': LoadGroupScheduling,
    'worksteal': WorkStealingScheduling,
}

def create_scheduler(config, log):
    dist_mode = config.getoption("dist")
    
    # First try custom scheduler hook
    custom = config.hook.pytest_xdist_make_scheduler(config=config, log=log)
    if custom:
        return custom
    
    # Use built-in scheduler
    scheduler_class = SCHEDULER_MAP.get(dist_mode, LoadScheduling)
    return scheduler_class(config, log)

Performance Considerations

# Choosing the right scheduler for your test suite

# For test suites with:
# - Varying execution times -> LoadScheduling (default)
# - Expensive class/module fixtures -> LoadScopeScheduling  
# - File-based fixtures/setup -> LoadFileScheduling
# - Tests that share state -> LoadGroupScheduling with markers
# - Large test suites -> WorkStealingScheduling
# - Cross-environment testing -> EachScheduling

def pytest_configure(config):
    """Auto-select scheduler based on test suite characteristics."""
    if not config.getoption("dist") or config.getoption("dist") == "no":
        return
        
    # Analyze test suite and suggest optimal scheduler
    test_count = len(config.getoption("file_or_dir") or [])
    
    if test_count > 1000:
        # Large test suite - work stealing might be better
        config.option.dist = "worksteal"
    elif has_expensive_fixtures():
        # Expensive fixtures - use scope grouping
        config.option.dist = "loadscope"

Install with Tessl CLI

npx tessl i tessl/pypi-pytest-xdist

docs

distribution-scheduling.md

hook-specifications.md

index.md

loop-on-fail.md

plugin-configuration.md

session-management.md

worker-detection.md

tile.json