Skip to content

Commit 01e37fe

Browse files
Merge pull request #3110 from nicoddemus/progress-teardown-3088
Fix progress report when tests fail during teardown
2 parents b0032ba + abbdb60 commit 01e37fe

File tree

3 files changed

+109
-27
lines changed

3 files changed

+109
-27
lines changed

_pytest/terminal.py

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,18 @@ def __init__(self, config, file=None):
152152
self.reportchars = getreportopt(config)
153153
self.hasmarkup = self._tw.hasmarkup
154154
self.isatty = file.isatty()
155-
self._progress_items_reported = 0
156-
self._show_progress_info = (self.config.getoption('capture') != 'no' and
157-
self.config.getini('console_output_style') == 'progress')
155+
self._progress_nodeids_reported = set()
156+
self._show_progress_info = self._determine_show_progress_info()
157+
158+
def _determine_show_progress_info(self):
159+
"""Return True if we should display progress information based on the current config"""
160+
# do not show progress if we are not capturing output (#3038)
161+
if self.config.getoption('capture') == 'no':
162+
return False
163+
# do not show progress if we are showing fixture setup/teardown
164+
if self.config.getoption('setupshow'):
165+
return False
166+
return self.config.getini('console_output_style') == 'progress'
158167

159168
def hasopt(self, char):
160169
char = {'xfailed': 'x', 'skipped': 's'}.get(char, char)
@@ -179,7 +188,6 @@ def write_ensure_prefix(self, prefix, extra="", **kwargs):
179188
if extra:
180189
self._tw.write(extra, **kwargs)
181190
self.currentfspath = -2
182-
self._write_progress_information_filling_space()
183191

184192
def ensure_newline(self):
185193
if self.currentfspath:
@@ -269,14 +277,13 @@ def pytest_runtest_logreport(self, report):
269277
# probably passed setup/teardown
270278
return
271279
running_xdist = hasattr(rep, 'node')
272-
self._progress_items_reported += 1
273280
if self.verbosity <= 0:
274281
if not running_xdist and self.showfspath:
275282
self.write_fspath_result(rep.nodeid, letter)
276283
else:
277284
self._tw.write(letter)
278-
self._write_progress_if_past_edge()
279285
else:
286+
self._progress_nodeids_reported.add(rep.nodeid)
280287
if markup is None:
281288
if rep.passed:
282289
markup = {'green': True}
@@ -289,6 +296,8 @@ def pytest_runtest_logreport(self, report):
289296
line = self._locationline(rep.nodeid, *rep.location)
290297
if not running_xdist:
291298
self.write_ensure_prefix(line, word, **markup)
299+
if self._show_progress_info:
300+
self._write_progress_information_filling_space()
292301
else:
293302
self.ensure_newline()
294303
self._tw.write("[%s]" % rep.node.gateway.id)
@@ -300,31 +309,28 @@ def pytest_runtest_logreport(self, report):
300309
self._tw.write(" " + line)
301310
self.currentfspath = -2
302311

303-
def _write_progress_if_past_edge(self):
304-
if not self._show_progress_info:
305-
return
306-
last_item = self._progress_items_reported == self._session.testscollected
307-
if last_item:
308-
self._write_progress_information_filling_space()
309-
return
310-
311-
past_edge = self._tw.chars_on_current_line + self._PROGRESS_LENGTH + 1 >= self._screen_width
312-
if past_edge:
313-
msg = self._get_progress_information_message()
314-
self._tw.write(msg + '\n', cyan=True)
312+
def pytest_runtest_logfinish(self, nodeid):
313+
if self.verbosity <= 0 and self._show_progress_info:
314+
self._progress_nodeids_reported.add(nodeid)
315+
last_item = len(self._progress_nodeids_reported) == self._session.testscollected
316+
if last_item:
317+
self._write_progress_information_filling_space()
318+
else:
319+
past_edge = self._tw.chars_on_current_line + self._PROGRESS_LENGTH + 1 >= self._screen_width
320+
if past_edge:
321+
msg = self._get_progress_information_message()
322+
self._tw.write(msg + '\n', cyan=True)
315323

316324
_PROGRESS_LENGTH = len(' [100%]')
317325

318326
def _get_progress_information_message(self):
319327
collected = self._session.testscollected
320328
if collected:
321-
progress = self._progress_items_reported * 100 // collected
329+
progress = len(self._progress_nodeids_reported) * 100 // collected
322330
return ' [{:3d}%]'.format(progress)
323331
return ' [100%]'
324332

325333
def _write_progress_information_filling_space(self):
326-
if not self._show_progress_info:
327-
return
328334
msg = self._get_progress_information_message()
329335
fill = ' ' * (self._tw.fullwidth - self._tw.chars_on_current_line - len(msg) - 1)
330336
self.write(fill + msg, cyan=True)

changelog/3088.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix progress percentage reported when tests fail during teardown.

testing/test_terminal.py

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,7 @@ def test_no_trailing_whitespace_after_inifile_word(testdir):
969969
class TestProgress:
970970

971971
@pytest.fixture
972-
def many_tests_file(self, testdir):
972+
def many_tests_files(self, testdir):
973973
testdir.makepyfile(
974974
test_bar="""
975975
import pytest
@@ -1006,30 +1006,30 @@ def pytest_collection_modifyitems(items, config):
10061006
'=* 2 passed in *=',
10071007
])
10081008

1009-
def test_normal(self, many_tests_file, testdir):
1009+
def test_normal(self, many_tests_files, testdir):
10101010
output = testdir.runpytest()
10111011
output.stdout.re_match_lines([
10121012
r'test_bar.py \.{10} \s+ \[ 50%\]',
10131013
r'test_foo.py \.{5} \s+ \[ 75%\]',
10141014
r'test_foobar.py \.{5} \s+ \[100%\]',
10151015
])
10161016

1017-
def test_verbose(self, many_tests_file, testdir):
1017+
def test_verbose(self, many_tests_files, testdir):
10181018
output = testdir.runpytest('-v')
10191019
output.stdout.re_match_lines([
10201020
r'test_bar.py::test_bar\[0\] PASSED \s+ \[ 5%\]',
10211021
r'test_foo.py::test_foo\[4\] PASSED \s+ \[ 75%\]',
10221022
r'test_foobar.py::test_foobar\[4\] PASSED \s+ \[100%\]',
10231023
])
10241024

1025-
def test_xdist_normal(self, many_tests_file, testdir):
1025+
def test_xdist_normal(self, many_tests_files, testdir):
10261026
pytest.importorskip('xdist')
10271027
output = testdir.runpytest('-n2')
10281028
output.stdout.re_match_lines([
10291029
r'\.{20} \s+ \[100%\]',
10301030
])
10311031

1032-
def test_xdist_verbose(self, many_tests_file, testdir):
1032+
def test_xdist_verbose(self, many_tests_files, testdir):
10331033
pytest.importorskip('xdist')
10341034
output = testdir.runpytest('-n2', '-v')
10351035
output.stdout.re_match_lines_random([
@@ -1038,10 +1038,85 @@ def test_xdist_verbose(self, many_tests_file, testdir):
10381038
r'\[gw\d\] \[\s*\d+%\] PASSED test_foobar.py::test_foobar\[1\]',
10391039
])
10401040

1041-
def test_capture_no(self, many_tests_file, testdir):
1041+
def test_capture_no(self, many_tests_files, testdir):
10421042
output = testdir.runpytest('-s')
10431043
output.stdout.re_match_lines([
10441044
r'test_bar.py \.{10}',
10451045
r'test_foo.py \.{5}',
10461046
r'test_foobar.py \.{5}',
10471047
])
1048+
1049+
1050+
class TestProgressWithTeardown:
1051+
"""Ensure we show the correct percentages for tests that fail during teardown (#3088)"""
1052+
1053+
@pytest.fixture
1054+
def contest_with_teardown_fixture(self, testdir):
1055+
testdir.makeconftest('''
1056+
import pytest
1057+
1058+
@pytest.fixture
1059+
def fail_teardown():
1060+
yield
1061+
assert False
1062+
''')
1063+
1064+
@pytest.fixture
1065+
def many_files(self, testdir, contest_with_teardown_fixture):
1066+
testdir.makepyfile(
1067+
test_bar='''
1068+
import pytest
1069+
@pytest.mark.parametrize('i', range(5))
1070+
def test_bar(fail_teardown, i):
1071+
pass
1072+
''',
1073+
test_foo='''
1074+
import pytest
1075+
@pytest.mark.parametrize('i', range(15))
1076+
def test_foo(fail_teardown, i):
1077+
pass
1078+
''',
1079+
)
1080+
1081+
def test_teardown_simple(self, testdir, contest_with_teardown_fixture):
1082+
testdir.makepyfile('''
1083+
def test_foo(fail_teardown):
1084+
pass
1085+
''')
1086+
output = testdir.runpytest()
1087+
output.stdout.re_match_lines([
1088+
r'test_teardown_simple.py \.E\s+\[100%\]',
1089+
])
1090+
1091+
def test_teardown_with_test_also_failing(self, testdir, contest_with_teardown_fixture):
1092+
testdir.makepyfile('''
1093+
def test_foo(fail_teardown):
1094+
assert False
1095+
''')
1096+
output = testdir.runpytest()
1097+
output.stdout.re_match_lines([
1098+
r'test_teardown_with_test_also_failing.py FE\s+\[100%\]',
1099+
])
1100+
1101+
def test_teardown_many(self, testdir, many_files):
1102+
output = testdir.runpytest()
1103+
output.stdout.re_match_lines([
1104+
r'test_bar.py (\.E){5}\s+\[ 25%\]',
1105+
r'test_foo.py (\.E){15}\s+\[100%\]',
1106+
])
1107+
1108+
def test_teardown_many_verbose(self, testdir, many_files):
1109+
output = testdir.runpytest('-v')
1110+
output.stdout.re_match_lines([
1111+
r'test_bar.py::test_bar\[0\] PASSED\s+\[ 5%\]',
1112+
r'test_bar.py::test_bar\[0\] ERROR\s+\[ 5%\]',
1113+
r'test_bar.py::test_bar\[4\] PASSED\s+\[ 25%\]',
1114+
r'test_bar.py::test_bar\[4\] ERROR\s+\[ 25%\]',
1115+
])
1116+
1117+
def test_xdist_normal(self, many_files, testdir):
1118+
pytest.importorskip('xdist')
1119+
output = testdir.runpytest('-n2')
1120+
output.stdout.re_match_lines([
1121+
r'[\.E]{40} \s+ \[100%\]',
1122+
])

0 commit comments

Comments
 (0)