Skip to content

Commit bfc030f

Browse files
authored
Add only_rerun and rerun_except kwargs to marker (#221)
This allows the command line arguments with the same names to be overridden on a per-test basis. The functionality is the same - each argument takes a regex string or list of regex strings.
1 parent 1923529 commit bfc030f

4 files changed

Lines changed: 112 additions & 5 deletions

File tree

CHANGES.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ Changelog
44
11.2 (unreleased)
55
-----------------
66

7-
- Nothing changed yet.
7+
Features
8+
++++++++
9+
10+
- Add ``only_rerun`` and ``rerun_except`` arguments to ``@pytest.mark.flaky`` marker.
811

912

1013
11.1.2 (2023-03-09)

README.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,27 @@ You can also specify an optional ``condition`` in the re-run marker:
142142
import random
143143
assert random.choice([True, False])
144144
145+
Exception filtering can be accomplished by specifying regular expressions for
146+
``only_rerun`` and ``rerun_except``. They override the ``--only-rerun`` and
147+
``--rerun-except`` command line arguments, respectively.
148+
149+
Arguments can be a single string:
150+
151+
.. code-block:: python
152+
153+
@pytest.mark.flaky(rerun_except="AssertionError")
154+
def test_example():
155+
raise AssertionError()
156+
157+
Or a list of strings:
158+
159+
.. code-block:: python
160+
161+
@pytest.mark.flaky(only_rerun=["AssertionError", "ValueError"])
162+
def test_example():
163+
raise AssertionError()
164+
165+
145166
You can use ``@pytest.mark.flaky(condition)`` similarly as ``@pytest.mark.skipif(condition)``, see `pytest-mark-skipif <https://docs.pytest.org/en/6.2.x/reference.html#pytest-mark-skipif>`_
146167

147168
.. code-block:: python

pytest_rerunfailures.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -302,12 +302,25 @@ def _remove_failed_setup_state_from_session(item):
302302
setup_state.stack = []
303303

304304

305-
def _should_hard_fail_on_error(session_config, report):
305+
def _get_rerun_filter_regex(item, regex_name):
306+
rerun_marker = _get_marker(item)
307+
308+
if rerun_marker is not None and regex_name in rerun_marker.kwargs:
309+
regex = rerun_marker.kwargs[regex_name]
310+
if isinstance(regex, str):
311+
regex = [regex]
312+
else:
313+
regex = getattr(item.session.config.option, regex_name)
314+
315+
return regex
316+
317+
318+
def _should_hard_fail_on_error(item, report):
306319
if report.outcome != "failed":
307320
return False
308321

309-
rerun_errors = session_config.option.only_rerun
310-
rerun_except_errors = session_config.option.rerun_except
322+
rerun_errors = _get_rerun_filter_regex(item, "only_rerun")
323+
rerun_except_errors = _get_rerun_filter_regex(item, "rerun_except")
311324

312325
if not rerun_errors and not rerun_except_errors:
313326

@@ -333,7 +346,7 @@ def _should_hard_fail_on_error(session_config, report):
333346

334347
def _should_not_rerun(item, report, reruns):
335348
xfail = hasattr(report, "wasxfail")
336-
is_terminal_error = _should_hard_fail_on_error(item.session.config, report)
349+
is_terminal_error = _should_hard_fail_on_error(item, report)
337350
condition = get_reruns_condition(item)
338351
return (
339352
item.execution_count > reruns

test_pytest_rerunfailures.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,76 @@ def test_fail_two():
659659
assert_outcomes(result, passed=0, failed=1, rerun=2)
660660

661661

662+
@pytest.mark.parametrize(
663+
"marker_only_rerun,cli_only_rerun,should_rerun",
664+
[
665+
("AssertionError", None, True),
666+
("AssertionError: ERR", None, True),
667+
(["AssertionError"], None, True),
668+
(["AssertionError: ABC"], None, False),
669+
("ValueError", None, False),
670+
(["ValueError"], None, False),
671+
(["AssertionError", "ValueError"], None, True),
672+
# CLI override behavior
673+
("AssertionError", "ValueError", True),
674+
("ValueError", "AssertionError", False),
675+
],
676+
)
677+
def test_only_rerun_flag_in_flaky_marker(
678+
testdir, marker_only_rerun, cli_only_rerun, should_rerun
679+
):
680+
testdir.makepyfile(
681+
f"""
682+
import pytest
683+
684+
@pytest.mark.flaky(reruns=1, only_rerun={marker_only_rerun!r})
685+
def test_fail():
686+
raise AssertionError("ERR")
687+
"""
688+
)
689+
args = []
690+
if cli_only_rerun:
691+
args.extend(["--only-rerun", cli_only_rerun])
692+
result = testdir.runpytest()
693+
num_reruns = 1 if should_rerun else 0
694+
assert_outcomes(result, passed=0, failed=1, rerun=num_reruns)
695+
696+
697+
@pytest.mark.parametrize(
698+
"marker_rerun_except,cli_rerun_except,should_rerun",
699+
[
700+
("AssertionError", None, False),
701+
("AssertionError: ERR", None, False),
702+
(["AssertionError"], None, False),
703+
(["AssertionError: ABC"], None, True),
704+
("ValueError", None, True),
705+
(["ValueError"], None, True),
706+
(["OSError", "ValueError"], None, True),
707+
# CLI override behavior
708+
("AssertionError", "ValueError", False),
709+
("ValueError", "AssertionError", True),
710+
],
711+
)
712+
def test_rerun_except_flag_in_flaky_marker(
713+
testdir, marker_rerun_except, cli_rerun_except, should_rerun
714+
):
715+
testdir.makepyfile(
716+
f"""
717+
import pytest
718+
719+
@pytest.mark.flaky(reruns=1, rerun_except={marker_rerun_except!r})
720+
def test_fail():
721+
raise AssertionError("ERR")
722+
"""
723+
)
724+
args = []
725+
if cli_rerun_except:
726+
args.extend(["--rerun-except", cli_rerun_except])
727+
result = testdir.runpytest(*args)
728+
num_reruns = 1 if should_rerun else 0
729+
assert_outcomes(result, passed=0, failed=1, rerun=num_reruns)
730+
731+
662732
def test_ini_file_parameters(testdir):
663733
testdir.makepyfile(
664734
"""

0 commit comments

Comments
 (0)