pytest-race
pytest-race is a plugin for the pytest testing framework that helps detect and test race conditions in your Python codebase. It introduces a `start_race` fixture, allowing you to easily run a target callable function multiple times concurrently in separate threads. The current version is 0.2.0, with a slow release cadence, last updated in June 2022.
Warnings
- gotcha When writing concurrent tests, be extremely careful with shared mutable state (like global variables or shared objects). The `pytest-race` fixture merely facilitates running code in multiple threads; it's up to the user to correctly design the test to expose and assert against race conditions, which often involves shared state that isn't properly synchronized.
- gotcha Pytest itself is single-threaded. Avoid using pytest-specific assertion helpers or primitives (e.g., `pytest.warns()`, `pytest.raises()`, `caplog`, `capsys`) directly from multiple threads spawned by `start_race`'s `target` callable, as these are not designed to be thread-safe.
- gotcha Race condition tests can be inherently flaky due to timing. Overly strict or poorly timed assertions can lead to intermittent failures that mask the actual race condition or lead to false positives.
Install
-
pip install pytest-race
Imports
- start_race
def test_my_race_condition(start_race):
Quickstart
import pytest
from time import sleep
from random import randint
ACCUMULATOR = 0 # This global var is race conditions prone.
def test_race_condition_example(start_race):
"""Demonstrates testing a race condition with pytest-race."""
global ACCUMULATOR
ACCUMULATOR = 0 # Reset for each test run
def actual_test_target():
global ACCUMULATOR
increment = randint(1, 10000)
accumulator_before = ACCUMULATOR
sleep(0.01) # Simulate some lag for race condition to manifest
ACCUMULATOR += increment
# Assert that the accumulator increased by exactly 'increment'
# (this assertion will likely fail due to race conditions).
# In a real test, you'd assert against expected concurrent behavior.
assert accumulator_before + increment == ACCUMULATOR
# Run 'actual_test_target' in 2 threads simultaneously
start_race(threads_num=2, target=actual_test_target)
# To run this test:
# 1. Save it as e.g., test_race_example.py
# 2. Run `pytest -s` (the -s flag is useful to see print output if you add any)