Skip to content
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

python: collect: ignore exceptions with isinstance #4284

Merged
merged 1 commit into from
Nov 1, 2018

Conversation

blueyed
Copy link
Contributor

@blueyed blueyed commented Oct 31, 2018

Fixes #4266.

@@ -195,7 +195,11 @@ def pytest_pycollect_makeitem(collector, name, obj):
if res is not None:
return
# nothing was collected elsewhere, let's do it here
if isclass(obj):
try:
Copy link
Member

Choose a reason for hiding this comment

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

if this was factored into a function called _hardened_isclass the test could be a unittest

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've factored it out and created a unit test, but kept the integration test, since it not only tests if _safe_isclass is really used during collection, but would also show other issues in the future with Django's settings.

@codecov
Copy link

codecov bot commented Nov 1, 2018

Codecov Report

Merging #4284 into master will increase coverage by 0.13%.
The diff coverage is 94.59%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4284      +/-   ##
==========================================
+ Coverage   95.65%   95.78%   +0.13%     
==========================================
  Files         109      109              
  Lines       24630    24666      +36     
  Branches     2396     2397       +1     
==========================================
+ Hits        23560    23627      +67     
+ Misses        759      738      -21     
+ Partials      311      301      -10
Flag Coverage Δ
#linux 95.66% <94.59%> (ø) ⬆️
#nobyte 91.37% <78.37%> (-0.03%) ⬇️
#numpy 41.6% <26.31%> (-0.02%) ⬇️
#pexpect 41.6% <26.31%> (-0.02%) ⬇️
#py27 94.03% <78.37%> (+0.1%) ⬆️
#py34 92.2% <75.67%> (ø) ⬆️
#py35 92.21% <75.67%> (ø) ⬆️
#py36 93.8% <75.67%> (-0.02%) ⬇️
#py37 92.35% <75.67%> (ø) ⬆️
#trial 41.6% <26.31%> (-0.02%) ⬇️
#windows 91.15% <78.37%> (?)
#xdist 93.74% <94.59%> (ø) ⬆️
Impacted Files Coverage Δ
src/_pytest/python.py 95.84% <100%> (ø) ⬆️
src/_pytest/compat.py 96.77% <100%> (+0.08%) ⬆️
testing/test_compat.py 91.13% <100%> (+0.86%) ⬆️
testing/test_collection.py 99.77% <100%> (ø) ⬆️
testing/python/raises.py 92.37% <88.88%> (-0.63%) ⬇️
src/_pytest/fixtures.py 97.38% <0%> (+0.26%) ⬆️
testing/test_capture.py 99.24% <0%> (+0.3%) ⬆️
src/_pytest/pytester.py 87.39% <0%> (+0.42%) ⬆️
src/_pytest/nodes.py 94.75% <0%> (+0.8%) ⬆️
src/_pytest/pathlib.py 89.61% <0%> (+2.18%) ⬆️
... and 3 more

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 56e6bb0...e30f709. Read the comment docs.

try:
return isclass(obj)
except Exception:
return False
Copy link
Member

Choose a reason for hiding this comment

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

should this live in pytest._compat, that way other uses of isclass get the same benefits?

seems to be also used in these places:

$ git grep compat.*isclass
src/_pytest/fixtures.py:from _pytest.compat import isclass
src/_pytest/python.py:from _pytest.compat import isclass
src/_pytest/python_api.py:from _pytest.compat import isclass

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've thought it could be moved when needed.
Currently there are several places that handle things safely already - i.e. with the test case for django.conf.settings I've seen it coming to the "raise" at least once before.
Therefore I've thought to keep it near to the single user for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I.e. I do not think we should wrap isclass/isinstance unnecessarily.

Copy link
Member

Choose a reason for hiding this comment

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

🤷‍♂️ it's triggerable in raises at least:

import pytest

class CrappyClass(Exception):
    @property
    def __class__(self):
        assert False, 'nuuuu'


with pytest.raises(CrappyClass()):
    pass
Traceback (most recent call last):
  File "t.py", line 9, in <module>
    with pytest.raises(CrappyClass()):
  File "/tmp/pytest/venv/lib/python3.6/site-packages/_pytest/python_api.py", line 654, in raises
    for exc in filterfalse(isclass, always_iterable(expected_exception, BASE_TYPE)):
  File "/tmp/pytest/venv/lib/python3.6/site-packages/more_itertools/more.py", line 1305, in always_iterable
    if (base_type is not None) and isinstance(obj, base_type):
  File "t.py", line 6, in __class__
    assert False, 'nuuuu'
AssertionError: nuuuu

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, might be good to see this for raises after all?! Not sure.
It's not as bad as with collection at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll move it to compat and use it for your test case, too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, it fails there through always_iterable already, which does:

if (base_type is not None) and isinstance(obj, base_type):

Fixing this to use for exc in filterfalse(safe_isclass, always_iterable(expected_exception, base_type=None)): (base_type=None) then makes it fail with:

TypeError: exceptions must be old-style classes or derived from BaseException, not <class 'test_compat.test_safe_getclass..CrappyClass'>

(beause it returns False)

Let's stick to "Arbitrary exceptions should not be silenced." here at least.

@asottile
Copy link
Member

asottile commented Nov 1, 2018

this seemed odd enough so I made a bpo issue for it: https://bugs.python.org/issue35137

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.

either way, this seems good -- thanks for this 🎉

@blueyed
Copy link
Contributor Author

blueyed commented Nov 1, 2018

I've reworked it a bit and added tests for raises.
Please re-review.

@blueyed
Copy link
Contributor Author

blueyed commented Nov 1, 2018

Amended a fixup:

diff --git a/testing/python/raises.py b/testing/python/raises.py
index 926a2d06..a72aeef6 100644
--- a/testing/python/raises.py
+++ b/testing/python/raises.py
@@ -181,9 +181,6 @@ class CrappyClass(Exception):
             def __class__(self):
                 assert False, "via __class__"
 
-            def __call__(self):
-                return CrappyClass
-
         if six.PY2:
             with pytest.raises(pytest.fail.Exception) as excinfo:
                 with pytest.raises(CrappyClass()):

@blueyed blueyed merged commit a5b3ad2 into pytest-dev:master Nov 1, 2018
@blueyed blueyed deleted the test-dunder-class branch November 1, 2018 21:15
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.

4 participants