diff --git a/AUTHORS b/AUTHORS index 9102aa87f..b79f8a9f9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -122,4 +122,4 @@ Tom Viner Trevor Bekolay Vasily Kuznetsov Wouter van Ackooy - +Xuecong Liao diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d4e5dfa60..b7ae89e61 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -166,6 +166,9 @@ time or change existing behaviors in order to make them less surprising/more use * Plugins now benefit from assertion rewriting. Thanks `@sober7`_, `@nicoddemus`_ and `@flub`_ for the PR. +* Highlight path of the file location in the error report to make it easier to copy/paste. + Thanks `@suzaku`_ for the PR (`#1778`_). + * Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like those marked with the ``@pytest.yield_fixture`` decorator. This change renders ``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements @@ -347,6 +350,7 @@ time or change existing behaviors in order to make them less surprising/more use .. _#1723: https://github.com/pytest-dev/pytest/pull/1723 .. _#1740: https://github.com/pytest-dev/pytest/issues/1740 .. _#1749: https://github.com/pytest-dev/pytest/issues/1749 +.. _#1778: https://github.com/pytest-dev/pytest/pull/1778 .. _#372: https://github.com/pytest-dev/pytest/issues/372 .. _#457: https://github.com/pytest-dev/pytest/issues/457 .. _#460: https://github.com/pytest-dev/pytest/pull/460 @@ -385,6 +389,7 @@ time or change existing behaviors in order to make them less surprising/more use .. _@RedBeardCode: https://github.com/RedBeardCode .. _@sallner: https://github.com/sallner .. _@sober7: https://github.com/sober7 +.. _@suzaku: https://github.com/suzaku .. _@Stranger6667: https://github.com/Stranger6667 .. _@tareqalayan: https://github.com/tareqalayan .. _@taschini: https://github.com/taschini diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py index 0af5a3465..040e4fc8d 100644 --- a/_pytest/_code/code.py +++ b/_pytest/_code/code.py @@ -785,7 +785,8 @@ class ReprFileLocation(TerminalRepr): i = msg.find("\n") if i != -1: msg = msg[:i] - tw.line("%s:%s: %s" %(self.path, self.lineno, msg)) + tw.write(self.path, bold=True, red=True) + tw.line(":%s: %s" % (self.lineno, msg)) class ReprLocals(TerminalRepr): def __init__(self, lines): diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 64d1ff89e..5f866c585 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -26,14 +26,23 @@ import pytest pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3])) class TWMock: + WRITE = object() + def __init__(self): self.lines = [] + self.is_writing = False def sep(self, sep, line=None): self.lines.append((sep, line)) + def write(self, msg, **kw): + self.lines.append((TWMock.WRITE, msg)) def line(self, line, **kw): self.lines.append(line) def markup(self, text, **kw): return text + def get_write_msg(self, idx): + flag, msg = self.lines[idx] + assert flag == TWMock.WRITE + return msg fullwidth = 80 @@ -803,14 +812,18 @@ raise ValueError() assert tw.lines[0] == " def f():" assert tw.lines[1] == "> g(3)" assert tw.lines[2] == "" - assert tw.lines[3].endswith("mod.py:5: ") - assert tw.lines[4] == ("_ ", None) - assert tw.lines[5] == "" - assert tw.lines[6] == " def g(x):" - assert tw.lines[7] == "> raise ValueError(x)" - assert tw.lines[8] == "E ValueError: 3" - assert tw.lines[9] == "" - assert tw.lines[10].endswith("mod.py:3: ValueError") + line = tw.get_write_msg(3) + assert line.endswith("mod.py") + assert tw.lines[4] == (":5: ") + assert tw.lines[5] == ("_ ", None) + assert tw.lines[6] == "" + assert tw.lines[7] == " def g(x):" + assert tw.lines[8] == "> raise ValueError(x)" + assert tw.lines[9] == "E ValueError: 3" + assert tw.lines[10] == "" + line = tw.get_write_msg(11) + assert line.endswith("mod.py") + assert tw.lines[12] == ":3: ValueError" def test_toterminal_long_missing_source(self, importasmod, tmpdir): mod = importasmod(""" @@ -829,13 +842,17 @@ raise ValueError() tw.lines.pop(0) assert tw.lines[0] == "> ???" assert tw.lines[1] == "" - assert tw.lines[2].endswith("mod.py:5: ") - assert tw.lines[3] == ("_ ", None) - assert tw.lines[4] == "" - assert tw.lines[5] == "> ???" - assert tw.lines[6] == "E ValueError: 3" - assert tw.lines[7] == "" - assert tw.lines[8].endswith("mod.py:3: ValueError") + line = tw.get_write_msg(2) + assert line.endswith("mod.py") + assert tw.lines[3] == ":5: " + assert tw.lines[4] == ("_ ", None) + assert tw.lines[5] == "" + assert tw.lines[6] == "> ???" + assert tw.lines[7] == "E ValueError: 3" + assert tw.lines[8] == "" + line = tw.get_write_msg(9) + assert line.endswith("mod.py") + assert tw.lines[10] == ":3: ValueError" def test_toterminal_long_incomplete_source(self, importasmod, tmpdir): mod = importasmod(""" @@ -854,13 +871,17 @@ raise ValueError() tw.lines.pop(0) assert tw.lines[0] == "> ???" assert tw.lines[1] == "" - assert tw.lines[2].endswith("mod.py:5: ") - assert tw.lines[3] == ("_ ", None) - assert tw.lines[4] == "" - assert tw.lines[5] == "> ???" - assert tw.lines[6] == "E ValueError: 3" - assert tw.lines[7] == "" - assert tw.lines[8].endswith("mod.py:3: ValueError") + line = tw.get_write_msg(2) + assert line.endswith("mod.py") + assert tw.lines[3] == ":5: " + assert tw.lines[4] == ("_ ", None) + assert tw.lines[5] == "" + assert tw.lines[6] == "> ???" + assert tw.lines[7] == "E ValueError: 3" + assert tw.lines[8] == "" + line = tw.get_write_msg(9) + assert line.endswith("mod.py") + assert tw.lines[10] == ":3: ValueError" def test_toterminal_long_filenames(self, importasmod): mod = importasmod(""" @@ -874,15 +895,18 @@ raise ValueError() try: repr = excinfo.getrepr(abspath=False) repr.toterminal(tw) - line = tw.lines[-1] x = py.path.local().bestrelpath(path) if len(x) < len(str(path)): - assert line == "mod.py:3: ValueError" + msg = tw.get_write_msg(-2) + assert msg == "mod.py" + assert tw.lines[-1] == ":3: ValueError" repr = excinfo.getrepr(abspath=True) repr.toterminal(tw) + msg = tw.get_write_msg(-2) + assert msg == path line = tw.lines[-1] - assert line == "%s:3: ValueError" %(path,) + assert line == ":3: ValueError" finally: old.chdir() @@ -929,19 +953,25 @@ raise ValueError() assert tw.lines[1] == " def f():" assert tw.lines[2] == "> g()" assert tw.lines[3] == "" - assert tw.lines[4].endswith("mod.py:3: ") - assert tw.lines[5] == ("_ ", None) - assert tw.lines[6].endswith("in g") - assert tw.lines[7] == " h()" - assert tw.lines[8].endswith("in h") - assert tw.lines[9] == " i()" - assert tw.lines[10] == ("_ ", None) - assert tw.lines[11] == "" - assert tw.lines[12] == " def i():" - assert tw.lines[13] == "> raise ValueError()" - assert tw.lines[14] == "E ValueError" - assert tw.lines[15] == "" - assert tw.lines[16].endswith("mod.py:9: ValueError") + msg = tw.get_write_msg(4) + assert msg.endswith("mod.py") + assert tw.lines[5] == ":3: " + assert tw.lines[6] == ("_ ", None) + tw.get_write_msg(7) + assert tw.lines[8].endswith("in g") + assert tw.lines[9] == " h()" + tw.get_write_msg(10) + assert tw.lines[11].endswith("in h") + assert tw.lines[12] == " i()" + assert tw.lines[13] == ("_ ", None) + assert tw.lines[14] == "" + assert tw.lines[15] == " def i():" + assert tw.lines[16] == "> raise ValueError()" + assert tw.lines[17] == "E ValueError" + assert tw.lines[18] == "" + msg = tw.get_write_msg(19) + msg.endswith("mod.py") + assert tw.lines[20] == ":9: ValueError" @pytest.mark.skipif("sys.version_info[0] < 3") def test_exc_chain_repr(self, importasmod):