Skip to content

Fix handle repr error with showlocals and verbose output #5133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

kondratyev-nv
Copy link
Contributor

Fixes #5089

Copy link
Member

@RonnyPfannschmidt RonnyPfannschmidt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this change the display style in case of an error value?

@codecov
Copy link

codecov bot commented Apr 16, 2019

Codecov Report

Merging #5133 into master will increase coverage by 0.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #5133      +/-   ##
==========================================
+ Coverage   96.05%   96.07%   +0.01%     
==========================================
  Files         114      114              
  Lines       25784    25812      +28     
  Branches     2550     2550              
==========================================
+ Hits        24768    24798      +30     
+ Misses        705      704       -1     
+ Partials      311      310       -1
Impacted Files Coverage Δ
src/_pytest/_io/saferepr.py 81.25% <100%> (+2.18%) ⬆️
testing/test_session.py 100% <100%> (ø) ⬆️
src/_pytest/_code/code.py 95.53% <100%> (+0.01%) ⬆️
testing/code/test_excinfo.py 96.82% <100%> (+0.03%) ⬆️
testing/acceptance_test.py 98.03% <0%> (+0.39%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9eac473...d99d859. Read the comment docs.

@codecov
Copy link

codecov bot commented Apr 16, 2019

Codecov Report

Merging #5133 into master will increase coverage by <.01%.
The diff coverage is 98.24%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #5133      +/-   ##
==========================================
+ Coverage   96.06%   96.06%   +<.01%     
==========================================
  Files         114      114              
  Lines       25790    25825      +35     
  Branches     2550     2550              
==========================================
+ Hits        24776    24810      +34     
- Misses        704      705       +1     
  Partials      310      310
Impacted Files Coverage Δ
src/_pytest/_io/saferepr.py 80.43% <100%> (+1.36%) ⬆️
testing/test_session.py 100% <100%> (ø) ⬆️
src/_pytest/_code/code.py 95.5% <100%> (-0.02%) ⬇️
testing/code/test_excinfo.py 96.76% <95.65%> (-0.03%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e87d3d7...bc00d0f. Read the comment docs.

@RonnyPfannschmidt
Copy link
Member

nevermind, i missed a key part of the invocation site, its fine

try:
# Try the vanilla repr and make sure that the result is a string
return call(x, *args)
except Exception:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the trackback is not used, just use except Exception as e

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make it clear, I have not changed this part, I've moved it from _callhelper. I thought exc_info was used to get the class of an exception. Would it be ok to use e.__class__.__name__ instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RonnyPfannschmidt Can you have a look, please? With e.__class__.__name__ it looks like

try:
    exc_name = e.__class__.__name__
except Exception:
    exc_name = "unknown"
try:
    exc_info = str(e)
except Exception:
    exc_info = "unknown"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I'd use as exc though.

Copy link
Contributor

@blueyed blueyed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this. Please see my comments.

@@ -676,7 +679,7 @@ def repr_locals(self, locals):
if self.truncate_locals:
str_repr = self._saferepr(value)
else:
str_repr = pprint.pformat(value)
str_repr = self._safeformat(value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use it directly?
I.e. not via _safeformat method - I see that this is done for _saferepr, but I do not think it is good to follow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I've just followed the way it has been called. I will change this to a direct call of safeformat and saferepr. Thank you.

import sys

from six.moves import reprlib


def _safe_format_call(call, x, *args):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be renamed to something more explicit like _call_and_format_exception.

try:
# Try the vanilla repr and make sure that the result is a string
return call(x, *args)
except Exception:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I'd use as exc though.

try:
exc_name = exc.__class__.__name__
except Exception:
exc_name = "unknown"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not covered in tests (https://codecov.io/gh/pytest-dev/pytest/compare/9eac4733c56cd1bac8abb6639e19976710fee139...4024a121e446f348c5ea839e002f3362b8c4978a/diff#D1-13).
And I think it is not possible to get an exception via this, is it? (/cc @asottile)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sadly it is:

class CryParty(object):
    @property
    def __class__(self):
        raise TypeError('boom!')
>>> CryParty()
<__main__.CryParty object at 0x7f2e3c41b8d0>
>>> CryParty().__class__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "t.py", line 4, in __class__
    raise TypeError('boom!')
TypeError: boom!
>>> type(CryParty()).__name__
'CryParty'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Thank you so much! I saw a decrease in the coverage, and I was looking without any luck for the whole evening to find a way to raise an exception with __class__. So I was even going to remove try .. except block.

Copy link
Member

@asottile asottile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good! maybe add a test with my broken __class__ example, and switch to type(...).__name__ since we don't need to support old-style-classes for exceptions (py27+)

try:
exc_name = exc.__class__.__name__
except Exception:
exc_name = "unknown"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sadly it is:

class CryParty(object):
    @property
    def __class__(self):
        raise TypeError('boom!')
>>> CryParty()
<__main__.CryParty object at 0x7f2e3c41b8d0>
>>> CryParty().__class__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "t.py", line 4, in __class__
    raise TypeError('boom!')
TypeError: boom!
>>> type(CryParty()).__name__
'CryParty'

@@ -134,6 +134,30 @@ def test_implicit_bad_repr1(self):
!= -1
)

def test_broken_repr_with(self, testdir):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with_showlocals_verbose perhaps?

@kondratyev-nv
Copy link
Contributor Author

I have not found any way for type(exc).__name__ to raise an exception, so I've removed the corresponding try .. except block in _call_and_format_exception in src/_pytest/_io/saferepr.py. If there is any possibility for this code to fail, I will be glad to return it and add more tests.

Copy link
Member

@asottile asottile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 this is great!

Copy link
Contributor

@blueyed blueyed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome.

Please squash the commits and force-push.

@kondratyev-nv kondratyev-nv force-pushed the fix-handle-repr-error-with-showlocals branch from b9dbe2c to bc00d0f Compare April 19, 2019 15:55
@blueyed blueyed merged commit 34bc594 into pytest-dev:master Apr 19, 2019
@blueyed
Copy link
Contributor

blueyed commented Apr 19, 2019

Thank you!

@kondratyev-nv kondratyev-nv deleted the fix-handle-repr-error-with-showlocals branch April 20, 2019 07:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

INTERNALERROR when error raised in __repr__ with showlocals and verbose output
5 participants