Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,17 @@ those fixtures are shared between threads.
as errors.


- Four corresponding markers:
- Five corresponding markers:
- `pytest.mark.parallel_threads_limit(n)` to mark a single test to
run in a maximum of `n` threads, even if the `--parallel-threads`
command-line argument is set to a higher value. This is useful if a
test uses resources that should be limited.
- `pytest.mark.parallel_threads(n)` to mark a test to always run in `n`
- `pytest.mark.force_parallel_threads(n)` to force a test to always run
in `n` threads, overriding both the `--parallel-threads` command-line
argument and any `parallel_threads_limit` marker. Use this when a
test must always be multi-threaded with a specific thread count.
- `pytest.mark.parallel_threads(n)` (deprecated when `n > 1` — use
`force_parallel_threads` instead) to mark a test to always run in `n`
threads. Note that this implies that the test will be multi-threaded
just because pytest-run-parallel is installed, even if
`--parallel-threads` is not passed at the command-line.
Expand Down Expand Up @@ -130,8 +135,9 @@ temporary directory in that fixture.
When using the fixtures `thread_index` and `iteration_index`, they should be
requested directly by tests, and will return 0 when requested by other fixtures.

**Note**: It's possible to specify `--parallel-threads=auto` or
`pytest.mark.parallel_threads("auto")` which will let
**Note**: It's possible to specify `--parallel-threads=auto`,
`pytest.mark.force_parallel_threads("auto")`, or
`pytest.mark.parallel_threads_limit("auto")` which will let
`pytest-run-parallel` choose the number of logical CPU cores available
to the testing process. If that cannot be determined, the number of
physical CPU cores will be used. If that fails as well, it will fall
Expand Down Expand Up @@ -260,7 +266,7 @@ Note that using `pytest-xdist` and setting `iterations` to a number
greater than one might cause tests to run even more times than intended.

The other mode of operation occurs at the individual test level, via the
`pytest.mark.parallel_threads` and `pytest.mark.iterations` markers:
`pytest.mark.force_parallel_threads` and `pytest.mark.iterations` markers:

```python
# test_file.py
Expand All @@ -270,15 +276,15 @@ import pytest
def my_fixture():
...

@pytest.mark.parallel_threads(2)
@pytest.mark.force_parallel_threads(2)
@pytest.mark.iterations(10)
def test_something_1():
# This test will be run in parallel using two concurrent threads
# and 10 times in each thread
...

@pytest.mark.parametrize('arg', [1, 2, 3])
@pytest.mark.parallel_threads(3)
@pytest.mark.force_parallel_threads(3)
def test_fixture(my_fixture, arg):
# pytest markers and fixtures are supported as well
...
Expand Down
14 changes: 10 additions & 4 deletions src/pytest_run_parallel/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,16 +436,22 @@ def pytest_configure(config):
config.addinivalue_line(
"markers",
"parallel_threads(n): run the given test function in parallel "
"using `n` threads. Note that if n is greater than 1, the test "
"run with this many threads even if the --parallel-threads "
"command-line argument is not passed. Use parallel_threads_limit "
"instead if you want to avoid this pitfall.",
"using `n` threads. Deprecated if `n` is greater than 1. Use either "
"the parallel_threads_limit or force_parallel_threads marker to make "
"your intention clearer.",
)
config.addinivalue_line(
"markers",
"parallel_threads_limit(n): run the given test function in parallel "
"using a maximum of `n` threads.",
)
config.addinivalue_line(
"markers",
"force_parallel_threads(n): force the given test function to run in "
"parallel using `n` threads, overriding the --parallel-threads "
"command-line argument and any parallel_threads_limit marker. Use "
"this to ensure a test always runs with a specific number of threads.",
)
config.addinivalue_line(
"markers",
"iterations(n): run the given test function `n` times in each thread",
Expand Down
11 changes: 9 additions & 2 deletions src/pytest_run_parallel/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ def auto_or_int(val):

def get_num_workers(item):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not worth fixing: the reason marker_used is set only when n_workers == 1 for some markers only makes sense if you read the code in the caller and understand what it's being used for. Seems like it could always be set when a marker is used (irrespective of n_workers == 1), and it wouldn't affect the caller's behavior?

Definitely not worth doing: For a semantically more meaningful return in Rust I'd use an enum with two variants (NumThreads(n) and ThreadUnsafe) and a method get_n_threads() that returns n for the former and 1 for the latter. I forget if Python Enums even let you do that sort of thing, though 😁

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the code got a little spaghettified in here unfortunately :(

I'm going to ignore this refactoring suggestion for a future date.

n_workers = get_configured_num_workers(item.config)
# TODO: deprecate in favor of parallel_threads_limit
marker_used = False
marker = item.get_closest_marker("parallel_threads")
if marker is not None:
marker_used = True
n_workers = auto_or_int(marker.args[0])
if n_workers > 1:
warnings.warn(
"Using the parallel_threads marker with a value greater than 1 is deprecated. Use parallel_threads_limit instead.",
"Using the parallel_threads marker with a value greater than 1 is deprecated. "
"Use parallel_threads_limit if you want to set a thread limit or "
"force_parallel_threads if you want to force a test to be multithreaded.",
DeprecationWarning,
stacklevel=2,
)
Expand All @@ -42,6 +43,12 @@ def get_num_workers(item):
if n_workers > val:
n_workers = val

force_marker = item.get_closest_marker("force_parallel_threads")
if force_marker is not None:
n_workers = auto_or_int(force_marker.args[0])
if n_workers == 1:
marker_used = True

return n_workers, marker_used


Expand Down
86 changes: 86 additions & 0 deletions tests/test_run_parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,92 @@ def test_parallel_threads_two():
)


def test_force_parallel_threads_marker(pytester):
pytester.makepyfile("""
import pytest

def test_default_threads(num_parallel_threads):
assert num_parallel_threads == 1

@pytest.mark.force_parallel_threads(4)
def test_forced_four(num_parallel_threads):
assert num_parallel_threads == 4

@pytest.mark.force_parallel_threads(1)
def test_forced_one(num_parallel_threads):
assert num_parallel_threads == 1
""")

result = pytester.runpytest("-v")
assert result.ret == 0
result.stdout.fnmatch_lines(
[
"*::test_default_threads PASSED*",
"*::test_forced_four PARALLEL PASSED*",
"*::test_forced_one PASSED*",
]
)


def test_force_parallel_threads_overrides_cli(pytester):
pytester.makepyfile("""
import pytest

def test_uses_cli(num_parallel_threads):
assert num_parallel_threads == 10

@pytest.mark.force_parallel_threads(3)
def test_forced_overrides_cli(num_parallel_threads):
assert num_parallel_threads == 3
""")

result = pytester.runpytest("--parallel-threads=10", "-v")
assert result.ret == 0
result.stdout.fnmatch_lines(
[
"*::test_uses_cli PARALLEL PASSED*",
"*::test_forced_overrides_cli PARALLEL PASSED*",
]
)


def test_force_parallel_threads_overrides_limit(pytester):
pytester.makepyfile("""
import pytest

@pytest.mark.force_parallel_threads(5)
@pytest.mark.parallel_threads_limit(2)
def test_force_beats_limit(num_parallel_threads):
assert num_parallel_threads == 5
""")

result = pytester.runpytest("--parallel-threads=10", "-v")
assert result.ret == 0
result.stdout.fnmatch_lines(
[
"*::test_force_beats_limit PARALLEL PASSED*",
]
)


def test_force_parallel_threads_no_deprecation_warning(pytester):
pytester.makepyfile("""
import pytest

@pytest.mark.force_parallel_threads(3)
def test_no_warning():
assert True
""")

result = pytester.runpytest("-v", "-W", "error::DeprecationWarning")
assert result.ret == 0
result.stdout.fnmatch_lines(
[
"*::test_no_warning PARALLEL PASSED*",
]
)


def test_forever_without_selected_tests(pytester):
pytester.makepyfile("")
result = pytester.runpytest("--forever", "-v")
Expand Down
Loading