Skip to content

Commit b380eb5

Browse files
authored
Merge pull request #1781 from nicoddemus/highlight-file-loc
Highlight the path of file location in error report
2 parents aa145fa + 31c5194 commit b380eb5

File tree

4 files changed

+120
-74
lines changed

4 files changed

+120
-74
lines changed

AUTHORS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,4 @@ Tom Viner
122122
Trevor Bekolay
123123
Vasily Kuznetsov
124124
Wouter van Ackooy
125-
125+
Xuecong Liao

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ time or change existing behaviors in order to make them less surprising/more use
166166
* Plugins now benefit from assertion rewriting. Thanks
167167
`@sober7`_, `@nicoddemus`_ and `@flub`_ for the PR.
168168

169+
* Highlight path of the file location in the error report to make it easier to copy/paste.
170+
Thanks `@suzaku`_ for the PR (`#1778`_).
171+
169172
* Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like
170173
those marked with the ``@pytest.yield_fixture`` decorator. This change renders
171174
``@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
347350
.. _#1723: https://github.com/pytest-dev/pytest/pull/1723
348351
.. _#1740: https://github.com/pytest-dev/pytest/issues/1740
349352
.. _#1749: https://github.com/pytest-dev/pytest/issues/1749
353+
.. _#1778: https://github.com/pytest-dev/pytest/pull/1778
350354
.. _#372: https://github.com/pytest-dev/pytest/issues/372
351355
.. _#457: https://github.com/pytest-dev/pytest/issues/457
352356
.. _#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
385389
.. _@RedBeardCode: https://github.com/RedBeardCode
386390
.. _@sallner: https://github.com/sallner
387391
.. _@sober7: https://github.com/sober7
392+
.. _@suzaku: https://github.com/suzaku
388393
.. _@Stranger6667: https://github.com/Stranger6667
389394
.. _@tareqalayan: https://github.com/tareqalayan
390395
.. _@taschini: https://github.com/taschini

_pytest/_code/code.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,8 @@ def toterminal(self, tw):
785785
i = msg.find("\n")
786786
if i != -1:
787787
msg = msg[:i]
788-
tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
788+
tw.write(self.path, bold=True, red=True)
789+
tw.line(":%s: %s" % (self.lineno, msg))
789790

790791
class ReprLocals(TerminalRepr):
791792
def __init__(self, lines):

testing/code/test_excinfo.py

Lines changed: 112 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,23 @@
2626
pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3]))
2727

2828
class TWMock:
29+
WRITE = object()
30+
2931
def __init__(self):
3032
self.lines = []
33+
self.is_writing = False
3134
def sep(self, sep, line=None):
3235
self.lines.append((sep, line))
36+
def write(self, msg, **kw):
37+
self.lines.append((TWMock.WRITE, msg))
3338
def line(self, line, **kw):
3439
self.lines.append(line)
3540
def markup(self, text, **kw):
3641
return text
42+
def get_write_msg(self, idx):
43+
flag, msg = self.lines[idx]
44+
assert flag == TWMock.WRITE
45+
return msg
3746

3847
fullwidth = 80
3948

@@ -803,14 +812,18 @@ def f():
803812
assert tw.lines[0] == " def f():"
804813
assert tw.lines[1] == "> g(3)"
805814
assert tw.lines[2] == ""
806-
assert tw.lines[3].endswith("mod.py:5: ")
807-
assert tw.lines[4] == ("_ ", None)
808-
assert tw.lines[5] == ""
809-
assert tw.lines[6] == " def g(x):"
810-
assert tw.lines[7] == "> raise ValueError(x)"
811-
assert tw.lines[8] == "E ValueError: 3"
812-
assert tw.lines[9] == ""
813-
assert tw.lines[10].endswith("mod.py:3: ValueError")
815+
line = tw.get_write_msg(3)
816+
assert line.endswith("mod.py")
817+
assert tw.lines[4] == (":5: ")
818+
assert tw.lines[5] == ("_ ", None)
819+
assert tw.lines[6] == ""
820+
assert tw.lines[7] == " def g(x):"
821+
assert tw.lines[8] == "> raise ValueError(x)"
822+
assert tw.lines[9] == "E ValueError: 3"
823+
assert tw.lines[10] == ""
824+
line = tw.get_write_msg(11)
825+
assert line.endswith("mod.py")
826+
assert tw.lines[12] == ":3: ValueError"
814827

815828
def test_toterminal_long_missing_source(self, importasmod, tmpdir):
816829
mod = importasmod("""
@@ -829,13 +842,17 @@ def f():
829842
tw.lines.pop(0)
830843
assert tw.lines[0] == "> ???"
831844
assert tw.lines[1] == ""
832-
assert tw.lines[2].endswith("mod.py:5: ")
833-
assert tw.lines[3] == ("_ ", None)
834-
assert tw.lines[4] == ""
835-
assert tw.lines[5] == "> ???"
836-
assert tw.lines[6] == "E ValueError: 3"
837-
assert tw.lines[7] == ""
838-
assert tw.lines[8].endswith("mod.py:3: ValueError")
845+
line = tw.get_write_msg(2)
846+
assert line.endswith("mod.py")
847+
assert tw.lines[3] == ":5: "
848+
assert tw.lines[4] == ("_ ", None)
849+
assert tw.lines[5] == ""
850+
assert tw.lines[6] == "> ???"
851+
assert tw.lines[7] == "E ValueError: 3"
852+
assert tw.lines[8] == ""
853+
line = tw.get_write_msg(9)
854+
assert line.endswith("mod.py")
855+
assert tw.lines[10] == ":3: ValueError"
839856

840857
def test_toterminal_long_incomplete_source(self, importasmod, tmpdir):
841858
mod = importasmod("""
@@ -854,13 +871,17 @@ def f():
854871
tw.lines.pop(0)
855872
assert tw.lines[0] == "> ???"
856873
assert tw.lines[1] == ""
857-
assert tw.lines[2].endswith("mod.py:5: ")
858-
assert tw.lines[3] == ("_ ", None)
859-
assert tw.lines[4] == ""
860-
assert tw.lines[5] == "> ???"
861-
assert tw.lines[6] == "E ValueError: 3"
862-
assert tw.lines[7] == ""
863-
assert tw.lines[8].endswith("mod.py:3: ValueError")
874+
line = tw.get_write_msg(2)
875+
assert line.endswith("mod.py")
876+
assert tw.lines[3] == ":5: "
877+
assert tw.lines[4] == ("_ ", None)
878+
assert tw.lines[5] == ""
879+
assert tw.lines[6] == "> ???"
880+
assert tw.lines[7] == "E ValueError: 3"
881+
assert tw.lines[8] == ""
882+
line = tw.get_write_msg(9)
883+
assert line.endswith("mod.py")
884+
assert tw.lines[10] == ":3: ValueError"
864885

865886
def test_toterminal_long_filenames(self, importasmod):
866887
mod = importasmod("""
@@ -874,15 +895,18 @@ def f():
874895
try:
875896
repr = excinfo.getrepr(abspath=False)
876897
repr.toterminal(tw)
877-
line = tw.lines[-1]
878898
x = py.path.local().bestrelpath(path)
879899
if len(x) < len(str(path)):
880-
assert line == "mod.py:3: ValueError"
900+
msg = tw.get_write_msg(-2)
901+
assert msg == "mod.py"
902+
assert tw.lines[-1] == ":3: ValueError"
881903

882904
repr = excinfo.getrepr(abspath=True)
883905
repr.toterminal(tw)
906+
msg = tw.get_write_msg(-2)
907+
assert msg == path
884908
line = tw.lines[-1]
885-
assert line == "%s:3: ValueError" %(path,)
909+
assert line == ":3: ValueError"
886910
finally:
887911
old.chdir()
888912

@@ -929,19 +953,25 @@ def i():
929953
assert tw.lines[1] == " def f():"
930954
assert tw.lines[2] == "> g()"
931955
assert tw.lines[3] == ""
932-
assert tw.lines[4].endswith("mod.py:3: ")
933-
assert tw.lines[5] == ("_ ", None)
934-
assert tw.lines[6].endswith("in g")
935-
assert tw.lines[7] == " h()"
936-
assert tw.lines[8].endswith("in h")
937-
assert tw.lines[9] == " i()"
938-
assert tw.lines[10] == ("_ ", None)
939-
assert tw.lines[11] == ""
940-
assert tw.lines[12] == " def i():"
941-
assert tw.lines[13] == "> raise ValueError()"
942-
assert tw.lines[14] == "E ValueError"
943-
assert tw.lines[15] == ""
944-
assert tw.lines[16].endswith("mod.py:9: ValueError")
956+
msg = tw.get_write_msg(4)
957+
assert msg.endswith("mod.py")
958+
assert tw.lines[5] == ":3: "
959+
assert tw.lines[6] == ("_ ", None)
960+
tw.get_write_msg(7)
961+
assert tw.lines[8].endswith("in g")
962+
assert tw.lines[9] == " h()"
963+
tw.get_write_msg(10)
964+
assert tw.lines[11].endswith("in h")
965+
assert tw.lines[12] == " i()"
966+
assert tw.lines[13] == ("_ ", None)
967+
assert tw.lines[14] == ""
968+
assert tw.lines[15] == " def i():"
969+
assert tw.lines[16] == "> raise ValueError()"
970+
assert tw.lines[17] == "E ValueError"
971+
assert tw.lines[18] == ""
972+
msg = tw.get_write_msg(19)
973+
msg.endswith("mod.py")
974+
assert tw.lines[20] == ":9: ValueError"
945975

946976
@pytest.mark.skipif("sys.version_info[0] < 3")
947977
def test_exc_chain_repr(self, importasmod):
@@ -971,44 +1001,54 @@ def h():
9711001
assert tw.lines[2] == " try:"
9721002
assert tw.lines[3] == "> g()"
9731003
assert tw.lines[4] == ""
974-
assert tw.lines[5].endswith("mod.py:6: ")
975-
assert tw.lines[6] == ("_ ", None)
976-
assert tw.lines[7] == ""
977-
assert tw.lines[8] == " def g():"
978-
assert tw.lines[9] == "> raise ValueError()"
979-
assert tw.lines[10] == "E ValueError"
980-
assert tw.lines[11] == ""
981-
assert tw.lines[12].endswith("mod.py:12: ValueError")
982-
assert tw.lines[13] == ""
983-
assert tw.lines[14] == "The above exception was the direct cause of the following exception:"
1004+
line = tw.get_write_msg(5)
1005+
assert line.endswith('mod.py')
1006+
assert tw.lines[6] == ':6: '
1007+
assert tw.lines[7] == ("_ ", None)
1008+
assert tw.lines[8] == ""
1009+
assert tw.lines[9] == " def g():"
1010+
assert tw.lines[10] == "> raise ValueError()"
1011+
assert tw.lines[11] == "E ValueError"
1012+
assert tw.lines[12] == ""
1013+
line = tw.get_write_msg(13)
1014+
assert line.endswith('mod.py')
1015+
assert tw.lines[14] == ':12: ValueError'
9841016
assert tw.lines[15] == ""
985-
assert tw.lines[16] == " def f():"
986-
assert tw.lines[17] == " try:"
987-
assert tw.lines[18] == " g()"
988-
assert tw.lines[19] == " except Exception as e:"
989-
assert tw.lines[20] == "> raise Err() from e"
990-
assert tw.lines[21] == "E test_exc_chain_repr0.mod.Err"
991-
assert tw.lines[22] == ""
992-
assert tw.lines[23].endswith("mod.py:8: Err")
1017+
assert tw.lines[16] == "The above exception was the direct cause of the following exception:"
1018+
assert tw.lines[17] == ""
1019+
assert tw.lines[18] == " def f():"
1020+
assert tw.lines[19] == " try:"
1021+
assert tw.lines[20] == " g()"
1022+
assert tw.lines[21] == " except Exception as e:"
1023+
assert tw.lines[22] == "> raise Err() from e"
1024+
assert tw.lines[23] == "E test_exc_chain_repr0.mod.Err"
9931025
assert tw.lines[24] == ""
994-
assert tw.lines[25] == "During handling of the above exception, another exception occurred:"
995-
assert tw.lines[26] == ""
996-
assert tw.lines[27] == " def f():"
997-
assert tw.lines[28] == " try:"
998-
assert tw.lines[29] == " g()"
999-
assert tw.lines[30] == " except Exception as e:"
1000-
assert tw.lines[31] == " raise Err() from e"
1001-
assert tw.lines[32] == " finally:"
1002-
assert tw.lines[33] == "> h()"
1003-
assert tw.lines[34] == ""
1004-
assert tw.lines[35].endswith("mod.py:10: ")
1005-
assert tw.lines[36] == ('_ ', None)
1026+
line = tw.get_write_msg(25)
1027+
assert line.endswith('mod.py')
1028+
assert tw.lines[26] == ":8: Err"
1029+
assert tw.lines[27] == ""
1030+
assert tw.lines[28] == "During handling of the above exception, another exception occurred:"
1031+
assert tw.lines[29] == ""
1032+
assert tw.lines[30] == " def f():"
1033+
assert tw.lines[31] == " try:"
1034+
assert tw.lines[32] == " g()"
1035+
assert tw.lines[33] == " except Exception as e:"
1036+
assert tw.lines[34] == " raise Err() from e"
1037+
assert tw.lines[35] == " finally:"
1038+
assert tw.lines[36] == "> h()"
10061039
assert tw.lines[37] == ""
1007-
assert tw.lines[38] == " def h():"
1008-
assert tw.lines[39] == "> raise AttributeError()"
1009-
assert tw.lines[40] == "E AttributeError"
1040+
line = tw.get_write_msg(38)
1041+
assert line.endswith('mod.py')
1042+
assert tw.lines[39] == ":10: "
1043+
assert tw.lines[40] == ('_ ', None)
10101044
assert tw.lines[41] == ""
1011-
assert tw.lines[42].endswith("mod.py:15: AttributeError")
1045+
assert tw.lines[42] == " def h():"
1046+
assert tw.lines[43] == "> raise AttributeError()"
1047+
assert tw.lines[44] == "E AttributeError"
1048+
assert tw.lines[45] == ""
1049+
line = tw.get_write_msg(46)
1050+
assert line.endswith('mod.py')
1051+
assert tw.lines[47] == ":15: AttributeError"
10121052

10131053

10141054
@pytest.mark.parametrize("style", ["short", "long"])

0 commit comments

Comments
 (0)