From 5939b336cd21c7ef7b7896ed80097765a0a22db7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 11 Jan 2018 20:22:18 -0200 Subject: [PATCH] Fix progress report when tests fail during teardown Fix #3088 --- _pytest/terminal.py | 39 +++++++++--------- changelog/3088.bugfix | 1 + testing/test_terminal.py | 87 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 100 insertions(+), 27 deletions(-) create mode 100644 changelog/3088.bugfix diff --git a/_pytest/terminal.py b/_pytest/terminal.py index b20bf5ea8..4c0fdadaa 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -152,9 +152,9 @@ class TerminalReporter: self.reportchars = getreportopt(config) self.hasmarkup = self._tw.hasmarkup self.isatty = file.isatty() - self._progress_items_reported = 0 - self._show_progress_info = (self.config.getoption('capture') != 'no' and - self.config.getini('console_output_style') == 'progress') + self._progress_nodeids_reported = set() + self._show_progress_info = (self.config.getoption('capture') != 'no' and not self.config.getoption('setupshow') + and self.config.getini('console_output_style') == 'progress') def hasopt(self, char): char = {'xfailed': 'x', 'skipped': 's'}.get(char, char) @@ -179,7 +179,6 @@ class TerminalReporter: if extra: self._tw.write(extra, **kwargs) self.currentfspath = -2 - self._write_progress_information_filling_space() def ensure_newline(self): if self.currentfspath: @@ -269,14 +268,13 @@ class TerminalReporter: # probably passed setup/teardown return running_xdist = hasattr(rep, 'node') - self._progress_items_reported += 1 if self.verbosity <= 0: if not running_xdist and self.showfspath: self.write_fspath_result(rep.nodeid, letter) else: self._tw.write(letter) - self._write_progress_if_past_edge() else: + self._progress_nodeids_reported.add(rep.nodeid) if markup is None: if rep.passed: markup = {'green': True} @@ -289,6 +287,8 @@ class TerminalReporter: line = self._locationline(rep.nodeid, *rep.location) if not running_xdist: self.write_ensure_prefix(line, word, **markup) + if self._show_progress_info: + self._write_progress_information_filling_space() else: self.ensure_newline() self._tw.write("[%s]" % rep.node.gateway.id) @@ -300,31 +300,28 @@ class TerminalReporter: self._tw.write(" " + line) self.currentfspath = -2 - def _write_progress_if_past_edge(self): - if not self._show_progress_info: - return - last_item = self._progress_items_reported == self._session.testscollected - if last_item: - self._write_progress_information_filling_space() - return - - past_edge = self._tw.chars_on_current_line + self._PROGRESS_LENGTH + 1 >= self._screen_width - if past_edge: - msg = self._get_progress_information_message() - self._tw.write(msg + '\n', cyan=True) + def pytest_runtest_logfinish(self, nodeid): + if self.verbosity <= 0 and self._show_progress_info: + self._progress_nodeids_reported.add(nodeid) + last_item = len(self._progress_nodeids_reported) == self._session.testscollected + if last_item: + self._write_progress_information_filling_space() + else: + past_edge = self._tw.chars_on_current_line + self._PROGRESS_LENGTH + 1 >= self._screen_width + if past_edge: + msg = self._get_progress_information_message() + self._tw.write(msg + '\n', cyan=True) _PROGRESS_LENGTH = len(' [100%]') def _get_progress_information_message(self): collected = self._session.testscollected if collected: - progress = self._progress_items_reported * 100 // collected + progress = len(self._progress_nodeids_reported) * 100 // collected return ' [{:3d}%]'.format(progress) return ' [100%]' def _write_progress_information_filling_space(self): - if not self._show_progress_info: - return msg = self._get_progress_information_message() fill = ' ' * (self._tw.fullwidth - self._tw.chars_on_current_line - len(msg) - 1) self.write(fill + msg, cyan=True) diff --git a/changelog/3088.bugfix b/changelog/3088.bugfix new file mode 100644 index 000000000..81b351571 --- /dev/null +++ b/changelog/3088.bugfix @@ -0,0 +1 @@ +Fix progress percentage reported when tests fail during teardown. diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 0db56f6f9..7dfa4b01e 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -969,7 +969,7 @@ def test_no_trailing_whitespace_after_inifile_word(testdir): class TestProgress: @pytest.fixture - def many_tests_file(self, testdir): + def many_tests_files(self, testdir): testdir.makepyfile( test_bar=""" import pytest @@ -1006,7 +1006,7 @@ class TestProgress: '=* 2 passed in *=', ]) - def test_normal(self, many_tests_file, testdir): + def test_normal(self, many_tests_files, testdir): output = testdir.runpytest() output.stdout.re_match_lines([ r'test_bar.py \.{10} \s+ \[ 50%\]', @@ -1014,7 +1014,7 @@ class TestProgress: r'test_foobar.py \.{5} \s+ \[100%\]', ]) - def test_verbose(self, many_tests_file, testdir): + def test_verbose(self, many_tests_files, testdir): output = testdir.runpytest('-v') output.stdout.re_match_lines([ r'test_bar.py::test_bar\[0\] PASSED \s+ \[ 5%\]', @@ -1022,14 +1022,14 @@ class TestProgress: r'test_foobar.py::test_foobar\[4\] PASSED \s+ \[100%\]', ]) - def test_xdist_normal(self, many_tests_file, testdir): + def test_xdist_normal(self, many_tests_files, testdir): pytest.importorskip('xdist') output = testdir.runpytest('-n2') output.stdout.re_match_lines([ r'\.{20} \s+ \[100%\]', ]) - def test_xdist_verbose(self, many_tests_file, testdir): + def test_xdist_verbose(self, many_tests_files, testdir): pytest.importorskip('xdist') output = testdir.runpytest('-n2', '-v') output.stdout.re_match_lines_random([ @@ -1038,10 +1038,85 @@ class TestProgress: r'\[gw\d\] \[\s*\d+%\] PASSED test_foobar.py::test_foobar\[1\]', ]) - def test_capture_no(self, many_tests_file, testdir): + def test_capture_no(self, many_tests_files, testdir): output = testdir.runpytest('-s') output.stdout.re_match_lines([ r'test_bar.py \.{10}', r'test_foo.py \.{5}', r'test_foobar.py \.{5}', ]) + + +class TestProgressWithTeardown: + """Ensure we show the correct percentages for tests that fail during teardown (#3088)""" + + @pytest.fixture + def contest_with_teardown_fixture(self, testdir): + testdir.makeconftest(''' + import pytest + + @pytest.fixture + def fail_teardown(): + yield + assert False + ''') + + @pytest.fixture + def many_files(self, testdir, contest_with_teardown_fixture): + testdir.makepyfile( + test_bar=''' + import pytest + @pytest.mark.parametrize('i', range(5)) + def test_bar(fail_teardown, i): + pass + ''', + test_foo=''' + import pytest + @pytest.mark.parametrize('i', range(15)) + def test_foo(fail_teardown, i): + pass + ''', + ) + + def test_teardown_simple(self, testdir, contest_with_teardown_fixture): + testdir.makepyfile(''' + def test_foo(fail_teardown): + pass + ''') + output = testdir.runpytest() + output.stdout.re_match_lines([ + r'test_teardown_simple.py \.E\s+\[100%\]', + ]) + + def test_teardown_with_test_also_failing(self, testdir, contest_with_teardown_fixture): + testdir.makepyfile(''' + def test_foo(fail_teardown): + assert False + ''') + output = testdir.runpytest() + output.stdout.re_match_lines([ + r'test_teardown_with_test_also_failing.py FE\s+\[100%\]', + ]) + + def test_teardown_many(self, testdir, many_files): + output = testdir.runpytest() + output.stdout.re_match_lines([ + r'test_bar.py (\.E){5}\s+\[ 25%\]', + r'test_foo.py (\.E){15}\s+\[100%\]', + ]) + + def test_teardown_many_verbose(self, testdir, many_files): + output = testdir.runpytest('-v') + output.stdout.re_match_lines([ + r'test_bar.py::test_bar\[0\] PASSED\s+\[ 5%\]', + r'test_bar.py::test_bar\[0\] ERROR\s+\[ 5%\]', + r'test_bar.py::test_bar\[4\] PASSED\s+\[ 25%\]', + r'test_bar.py::test_bar\[4\] ERROR\s+\[ 25%\]', + ]) + + def test_xdist_normal(self, many_files, testdir): + pytest.importorskip('xdist') + output = testdir.runpytest('-n2') + output.stdout.re_match_lines([ + r'[\.E]{40} \s+ \[100%\]', + ])