Skip to content

Commit c564938

Browse files
authored
Merge pull request #113 from gnikonorov/issue_101
Add --only-rerun flag to add option to rerun specific errors
2 parents e037356 + f44f18a commit c564938

4 files changed

Lines changed: 75 additions & 2 deletions

File tree

CHANGES.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Changelog
55
----------------
66

77
- Drop dependency on ``mock``.
8-
8+
- Add in new flag ``--only-rerun`` to allow for users to rerun only certain errors.
99

1010
9.0 (2020-03-18)
1111
----------------

README.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,24 @@ test re-run is launched:
5555
5656
$ pytest --reruns 5 --reruns-delay 1
5757
58+
Re-run all failures matching certain expressions
59+
------------------------------------------------
60+
61+
To re-run only those failures that match a certain list of expressions, use the
62+
``--only-rerun`` flag and pass it a regular expression. For example, the following would
63+
only rerun those errors that match ``AssertionError``:
64+
65+
.. code-block:: bash
66+
67+
$ pytest --reruns 5 --only-rerun AssertionError
68+
69+
Passing the flag multiple times accumulates the arguments, so the following would only rerun
70+
those errors that match ``AssertionError`` or ``ValueError``:
71+
72+
.. code-block:: bash
73+
74+
$ pytest --reruns 5 --only-rerun AssertionError --only-rerun ValueError
75+
5876
Re-run individual failures
5977
--------------------------
6078

pytest_rerunfailures.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pkg_resources
2+
import re
23
import time
34
import warnings
45

@@ -30,6 +31,14 @@ def pytest_addoption(parser):
3031
group = parser.getgroup(
3132
"rerunfailures",
3233
"re-run failing tests to eliminate flaky failures")
34+
group._addoption(
35+
'--only-rerun',
36+
action='append',
37+
dest='only_rerun',
38+
type=str,
39+
default=None,
40+
help='If passed, only rerun errors matching the regex provided. Pass this flag multiple times to accumulate a list of regexes to match'
41+
)
3342
group._addoption(
3443
'--reruns',
3544
action="store",
@@ -173,6 +182,21 @@ def _remove_failed_setup_state_from_session(item):
173182
setup_state.stack = list()
174183

175184

185+
def _should_hard_fail_on_error(session_config, report):
186+
if report.outcome != 'failed':
187+
return False
188+
189+
rerun_errors = session_config.option.only_rerun
190+
if not rerun_errors:
191+
return False
192+
193+
for rerun_regex in rerun_errors:
194+
if re.search(rerun_regex, report.longrepr.reprcrash.message):
195+
return False
196+
197+
return True
198+
199+
176200
def pytest_runtest_protocol(item, nextitem):
177201
"""
178202
Note: when teardown fails, two reports are generated for the case, one for
@@ -200,9 +224,10 @@ def pytest_runtest_protocol(item, nextitem):
200224
reports = runtestprotocol(item, nextitem=nextitem, log=False)
201225

202226
for report in reports: # 3 reports: setup, call, teardown
227+
is_terminal_error = _should_hard_fail_on_error(item.session.config, report)
203228
report.rerun = item.execution_count - 1
204229
xfail = hasattr(report, 'wasxfail')
205-
if item.execution_count > reruns or not report.failed or xfail:
230+
if item.execution_count > reruns or not report.failed or xfail or is_terminal_error:
206231
# last run or no failure detected, log normally
207232
item.ihook.pytest_runtest_logreport(report=report)
208233
else:

test_pytest_rerunfailures.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,33 @@ def pytest_runtest_logfinish(nodeid, location):
407407
""".format(hook_message))
408408
result = testdir.runpytest('--reruns', '1', '-s')
409409
result.stdout.fnmatch_lines(hook_message)
410+
411+
@pytest.mark.parametrize(
412+
"file_text, only_rerun_texts, should_rerun",
413+
[
414+
('def test_only_rerun(): raise AssertionError("ERR")', ['AssertionError'], True),
415+
('def test_only_rerun(): raise AssertionError("ERR")', ['Assertion*'], True),
416+
('def test_only_rerun(): raise AssertionError("ERR")', ['Assertion'], True),
417+
('def test_only_rerun(): raise AssertionError("ERR")', ['ValueError'], False),
418+
('def test_only_rerun(): raise AssertionError("ERR")', [''], True),
419+
('def test_only_rerun(): raise AssertionError("ERR")', ['AssertionError: '], True),
420+
('def test_only_rerun(): raise AssertionError("ERR")', ['AssertionError: ERR'], True),
421+
('def test_only_rerun(): raise AssertionError("ERR")', ['ERR'], True),
422+
('def test_only_rerun(): raise AssertionError("ERR")', ['AssertionError,ValueError'], False),
423+
('def test_only_rerun(): raise AssertionError("ERR")', ['AssertionError ValueError'], False),
424+
('def test_only_rerun(): raise AssertionError("ERR")', ['AssertionError', 'ValueError'], True),
425+
]
426+
)
427+
def test_only_rerun_flag(testdir, file_text, only_rerun_texts, should_rerun):
428+
testdir.makepyfile(file_text)
429+
430+
num_failed = 1
431+
num_passed = 0
432+
num_reruns = 1
433+
num_reruns_actual = num_reruns if should_rerun else 0
434+
435+
pytest_args = ['--reruns', str(num_reruns)]
436+
for only_rerun_text in only_rerun_texts:
437+
pytest_args.extend(['--only-rerun', only_rerun_text])
438+
result = testdir.runpytest(*pytest_args)
439+
assert_outcomes(result, passed=num_passed, failed=num_failed, rerun=num_reruns_actual)

0 commit comments

Comments
 (0)