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
13 changes: 13 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,19 @@ would only rerun those errors that does not match with ``AssertionError`` or ``O

$ pytest --reruns 5 --rerun-except AssertionError --rerun-except OSError

Exclude test paths from re-runs
--------------------------------

To exclude a test file or all tests under a directory from re-runs, use the
``--rerun-exclude-path`` option. Relative paths are interpreted relative to
pytest's root directory:

.. code-block:: bash

$ pytest --reruns 3 --rerun-exclude-path docs

The option may be passed multiple times to exclude multiple paths.

Re-run individual failures
--------------------------

Expand Down
1 change: 1 addition & 0 deletions changes/232.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a repeatable ``--rerun-exclude-path`` option to exclude test files or directories from reruns.
21 changes: 21 additions & 0 deletions src/pytest_rerunfailures.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ def pytest_addoption(parser):
"regex provided. Pass this flag multiple times to accumulate a list "
"of regexes to match",
)
group._addoption(
"--rerun-exclude-path",
action="append",
dest="rerun_exclude_path",
type=str,
default=None,
help="If passed, do not rerun tests in the path provided. "
"Pass this flag multiple times to accumulate a list of paths "
"to exclude",
)
group._addoption(
"--reruns-mode",
action="store",
Expand Down Expand Up @@ -847,6 +857,14 @@ def _restore_suspended_finalizers(item):
suspended_finalizers.clear()


def _is_rerun_path_excluded(item):
excluded_paths = item.config.getoption("rerun_exclude_path") or []
return any(
item.path.is_relative_to(item.config.rootpath / excluded_path)
for excluded_path in excluded_paths
)


def pytest_runtest_teardown(item, nextitem):
reruns = get_reruns_count(item)
if reruns is None:
Expand Down Expand Up @@ -918,6 +936,9 @@ def pytest_runtest_protocol(item, nextitem):
Note: when teardown fails, two reports are generated for the case, one for
the test case and the other for the teardown error.
"""
if _is_rerun_path_excluded(item):
return

reruns = get_reruns_count(item)
if reruns is None:
# global setting is not specified, and this test is not marked with
Expand Down
156 changes: 156 additions & 0 deletions tests/test_pytest_rerunfailures.py
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,162 @@ def test_rerun_except_flag(testdir, rerun_except_texts, should_rerun):
)


def test_rerun_exclude_path_prevents_reruns_in_directory(testdir):
testdir.makeconftest(
"""
from pathlib import Path

def pytest_runtest_call(item):
with Path("executions").open("a") as execution_log:
execution_log.write(item.name + "\\n")
"""
)
testdir.makepyfile(
test_normal="""
def test_normal_failure():
assert False
"""
)
excluded_dir = testdir.mkdir("excluded")
excluded_dir.join("test_excluded.py").write(
"""
def test_excluded_failure():
assert False
"""
)

result = testdir.runpytest("--reruns", "2", "--rerun-exclude-path", "excluded")

assert_outcomes(result, passed=0, failed=2, rerun=2)
executions = testdir.tmpdir.join("executions").read().splitlines()
assert executions.count("test_normal_failure") == 3
assert executions.count("test_excluded_failure") == 1


def test_rerun_exclude_path_prevents_reruns_for_file(testdir):
testdir.makeconftest(
"""
from pathlib import Path

def pytest_runtest_call(item):
with Path("executions").open("a") as execution_log:
execution_log.write(item.name + "\\n")
"""
)
testdir.makepyfile(
test_normal="""
def test_normal_failure():
assert False
"""
)
excluded_dir = testdir.mkdir("excluded")
excluded_dir.join("test_excluded.py").write(
"""
def test_excluded_failure():
assert False
"""
)

result = testdir.runpytest(
"--reruns",
"2",
"--rerun-exclude-path",
"excluded/test_excluded.py",
)

assert_outcomes(result, passed=0, failed=2, rerun=2)
executions = testdir.tmpdir.join("executions").read().splitlines()
assert executions.count("test_normal_failure") == 3
assert executions.count("test_excluded_failure") == 1


def test_rerun_exclude_path_accepts_multiple_paths(testdir):
testdir.makeconftest(
"""
from pathlib import Path

def pytest_runtest_call(item):
with Path("executions").open("a") as execution_log:
execution_log.write(item.name + "\\n")
"""
)
testdir.makepyfile(
test_normal="""
def test_normal_failure():
assert False
"""
)
excluded_a_dir = testdir.mkdir("excluded_a")
excluded_a_dir.join("test_excluded_a.py").write(
"""
def test_excluded_a_failure():
assert False
"""
)
excluded_b_dir = testdir.mkdir("excluded_b")
excluded_b_dir.join("test_excluded_b.py").write(
"""
def test_excluded_b_failure():
assert False
"""
)

result = testdir.runpytest(
"--reruns",
"2",
"--rerun-exclude-path",
"excluded_a",
"--rerun-exclude-path",
"excluded_b",
)

assert_outcomes(result, passed=0, failed=3, rerun=2)
executions = testdir.tmpdir.join("executions").read().splitlines()
assert executions.count("test_normal_failure") == 3
assert executions.count("test_excluded_a_failure") == 1
assert executions.count("test_excluded_b_failure") == 1


def test_rerun_exclude_path_prevents_reruns_for_doctest(testdir):
testdir.makeconftest(
"""
from pathlib import Path

def pytest_runtest_call(item):
with Path("executions").open("a") as execution_log:
execution_log.write(item.name + "\\n")
"""
)
testdir.makepyfile(
test_normal="""
def test_normal_failure():
assert False
"""
)
docs_dir = testdir.mkdir("docs")
docs_dir.join("guide.txt").write(
"""
This example fails:

>>> 1 + 1
3
"""
)

result = testdir.runpytest(
"--doctest-glob=*.txt",
"--reruns",
"2",
"--rerun-exclude-path",
"docs/guide.txt",
)

assert_outcomes(result, passed=0, failed=2, rerun=2)
executions = testdir.tmpdir.join("executions").read().splitlines()
assert executions.count("test_normal_failure") == 3
assert executions.count("guide.txt") == 1


@pytest.mark.parametrize(
"only_rerun_texts, rerun_except_texts, should_rerun",
[
Expand Down
Loading