pytest xdist plugin for distributed testing, most importantly across multiple CPUs
—
Legacy functionality for automatically re-running failed tests when files change. This feature is deprecated and will be removed in pytest-xdist 4.0.
⚠️ DEPRECATION WARNING: The --looponfail command line argument and looponfailroots config variable are deprecated. The loop-on-fail feature will be removed in pytest-xdist 4.0.
Entry point for loop-on-fail functionality that continuously monitors for file changes and re-runs failed tests.
def pytest_cmdline_main(config: pytest.Config) -> int | None:
"""
Handle looponfail command line option.
Args:
config: pytest configuration object
Returns:
int: Exit code 2 for looponfail mode, None for normal processing
Raises:
pytest.UsageError: If --pdb is used with --looponfail (incompatible)
"""
def looponfail_main(config: pytest.Config) -> None:
"""
Main loop-on-fail functionality.
Continuously monitors files for changes and re-runs failed tests
until all tests pass or interrupted with Ctrl-C.
Args:
config: pytest configuration object
"""Controls remote test execution in a subprocess for loop-on-fail mode.
class RemoteControl:
"""Controls remote test execution for loop-on-fail."""
def __init__(self, config: pytest.Config) -> None:
"""
Initialize remote control.
Args:
config: pytest configuration object
"""
def setup(self) -> None:
"""Set up remote worker session for test execution."""
def teardown(self) -> None:
"""Tear down remote worker session."""
def loop_once(self) -> None:
"""Execute one iteration of the test loop."""Monitors file system changes to trigger test re-runs.
class StatRecorder:
"""Records and monitors file system changes."""
def __init__(self, rootdirs: Sequence[Path]) -> None:
"""
Initialize file system monitor.
Args:
rootdirs: directories to monitor for changes
"""
def waitonchange(self, checkinterval: float = 1.0) -> None:
"""
Wait for file system changes.
Args:
checkinterval: interval in seconds between checks
"""Command line options for loop-on-fail functionality.
def pytest_addoption(parser: pytest.Parser) -> None:
"""
Add looponfail command line option.
Adds the -f/--looponfail option to pytest's argument parser.
Args:
parser: pytest argument parser
"""Options:
-f/--looponfail: Run tests in subprocess, wait for file modifications, then re-run failing test set until all passConfiguration:
looponfailroots: List of directories to monitor for changes (ini file setting)# DEPRECATED - will be removed in pytest-xdist 4.0
pytest -f
# With specific directories to monitor
pytest -f --looponfailroots=src,tests# In pytest.ini or tox.ini - DEPRECATED
[tool:pytest]
looponfailroots = src tests lib# DEPRECATED - for reference only
import pytest
from xdist.looponfail import looponfail_main
def run_loop_on_fail():
"""Example of programmatic loop-on-fail usage."""
# Create pytest config
config = pytest.Config()
config.option.looponfail = True
try:
looponfail_main(config)
except KeyboardInterrupt:
print("Loop-on-fail interrupted by user")Since loop-on-fail is deprecated, consider these alternatives:
# Install pytest-watch as replacement
pip install pytest-watch
# Use ptw instead of pytest -f
ptw --runner "pytest -x"# custom_watch.py - Simple replacement
import time
import subprocess
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class TestRunner(FileSystemEventHandler):
def __init__(self):
self.last_run = 0
def on_modified(self, event):
if event.is_directory:
return
# Debounce rapid file changes
now = time.time()
if now - self.last_run < 2:
return
self.last_run = now
# Run tests
print(f"File {event.src_path} changed, running tests...")
result = subprocess.run(['pytest', '--tb=short'],
capture_output=True, text=True)
if result.returncode == 0:
print("All tests passed!")
else:
print("Tests failed, watching for changes...")
if __name__ == "__main__":
event_handler = TestRunner()
observer = Observer()
observer.schedule(event_handler, path='.', recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()Modern IDEs provide built-in test watching functionality:
# VS Code with Python extension
# - Enable "python.testing.autoTestDiscoverOnSaveEnabled"
# - Use Test Explorer for continuous testing
# PyCharm Professional
# - Use "Run with Coverage" in continuous mode
# - Enable "Rerun tests automatically"Replace -f/--looponfail:
# Old (deprecated)
pytest -f
# New alternatives
pip install pytest-watch
ptw --runner "pytest -x"
# Or use IDE integrationReplace looponfailroots configuration:
# Old pytest.ini
[tool:pytest]
looponfailroots = src tests
# New with pytest-watch
ptw --runner "pytest -x" src testsUpdate CI/CD pipelines:
# Remove looponfail from CI scripts
# It was never intended for CI use anyway
# Old
- run: pytest -f # Don't do this in CI
# Correct
- run: pytest # Standard test executionDevelopment workflow adjustment:
# Instead of pytest -f, use:
# Option 1: pytest-watch
ptw
# Option 2: IDE auto-test features
# Configure your IDE for automatic test execution
# Option 3: Custom file watching
# Implement custom solution using watchdog or similarInstall with Tessl CLI
npx tessl i tessl/pypi-pytest-xdist