Skip to content

python: collect: ignore exceptions with isinstance#4284

Merged
blueyed merged 1 commit into
pytest-dev:masterfrom
blueyed:test-dunder-class
Nov 1, 2018
Merged

python: collect: ignore exceptions with isinstance#4284
blueyed merged 1 commit into
pytest-dev:masterfrom
blueyed:test-dunder-class

Conversation

@blueyed

@blueyed blueyed commented Oct 31, 2018

Copy link
Copy Markdown
Contributor

Fixes #4266.

Comment thread src/_pytest/python.py Outdated
return
# nothing was collected elsewhere, let's do it here
if isclass(obj):
try:

Copy link
Copy Markdown
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
Copy Markdown
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

codecov Bot commented Nov 1, 2018

Copy link
Copy Markdown

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.

Comment thread src/_pytest/python.py Outdated
try:
return isclass(obj)
except Exception:
return False

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

asottile commented Nov 1, 2018

Copy link
Copy Markdown
Member

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

@asottile asottile left a comment

Copy link
Copy Markdown
Member

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

blueyed commented Nov 1, 2018

Copy link
Copy Markdown
Contributor Author

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

@blueyed

blueyed commented Nov 1, 2018

Copy link
Copy Markdown
Contributor Author

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