From 815ab98410d3f0b4b15b782e54ee4febaa9ef223 Mon Sep 17 00:00:00 2001 From: Andrei Alexandru Date: Sun, 11 Dec 2022 10:39:23 -0800 Subject: [PATCH] Add B017 support for pytest.raises (#317) * Add B017 support for pytest.raises * Add pytest as optional dev dependency --- bugbear.py | 26 +++++++++++++++++++------- pyproject.toml | 2 ++ tests/b017.py | 14 +++++++++++++- tests/test_bugbear.py | 2 +- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/bugbear.py b/bugbear.py index 4e31763..2bc12c9 100644 --- a/bugbear.py +++ b/bugbear.py @@ -514,6 +514,7 @@ def check_for_b016(self, node): def check_for_b017(self, node): """Checks for use of the evil syntax 'with assertRaises(Exception):' + or 'with pytest.raises(Exception)'. This form of assertRaises will catch everything that subclasses Exception, which happens to be the vast majority of Python internal @@ -523,10 +524,21 @@ def check_for_b017(self, node): """ item = node.items[0] item_context = item.context_expr + if ( hasattr(item_context, "func") - and hasattr(item_context.func, "attr") - and item_context.func.attr == "assertRaises" + and ( + ( + hasattr(item_context.func, "attr") + and item_context.func.attr == "assertRaises" + ) + or ( + isinstance(item_context.func, ast.Attribute) + and item_context.func.attr == "raises" + and isinstance(item_context.func.value, ast.Name) + and item_context.func.value.id == "pytest" + ) + ) and len(item_context.args) == 1 and isinstance(item_context.args[0], ast.Name) and item_context.args[0].id == "Exception" @@ -1282,11 +1294,11 @@ def visit_Lambda(self, node): ) B017 = Error( message=( - "B017 assertRaises(Exception): should be considered evil. " - "It can lead to your test passing even if the code being tested is " - "never executed due to a typo. Either assert for a more specific " - "exception (builtin or custom), use assertRaisesRegex, or use the " - "context manager form of assertRaises." + "B017 assertRaises(Exception): or pytest.raises(Exception) should " + "be considered evil. It can lead to your test passing even if the " + "code being tested is never executed due to a typo. Either assert " + "for a more specific exception (builtin or custom), use " + "assertRaisesRegex, or use the context manager form of assertRaises." ) ) B018 = Error( diff --git a/pyproject.toml b/pyproject.toml index dcabd39..1f007ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ classifiers = [ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Quality Assurance", @@ -52,6 +53,7 @@ dev = [ "hypothesis", "hypothesmith>=0.2", "pre-commit", + "pytest", ] [tool.setuptools] diff --git a/tests/b017.py b/tests/b017.py index 6332fc4..1994a60 100644 --- a/tests/b017.py +++ b/tests/b017.py @@ -1,10 +1,12 @@ """ Should emit: -B017 - on lines 20 +B017 - on lines 24 and 26. """ import asyncio import unittest +import pytest + CONSTANT = True @@ -21,16 +23,26 @@ class Foobar(unittest.TestCase): def evil_raises(self) -> None: with self.assertRaises(Exception): raise Exception("Evil I say!") + with pytest.raises(Exception): + raise Exception("Evil I say!") def context_manager_raises(self) -> None: with self.assertRaises(Exception) as ex: raise Exception("Context manager is good") + with pytest.raises(Exception) as pyt_ex: + raise Exception("Context manager is good") + self.assertEqual("Context manager is good", str(ex.exception)) + self.assertEqual("Context manager is good", str(pyt_ex.exception)) def regex_raises(self) -> None: with self.assertRaisesRegex(Exception, "Regex is good"): raise Exception("Regex is good") + with pytest.raises(Exception, "Regex is good"): + raise Exception("Regex is good") def raises_with_absolute_reference(self): with self.assertRaises(asyncio.CancelledError): Foo() + with pytest.raises(asyncio.CancelledError): + Foo() diff --git a/tests/test_bugbear.py b/tests/test_bugbear.py index 1948428..0c40efa 100644 --- a/tests/test_bugbear.py +++ b/tests/test_bugbear.py @@ -252,7 +252,7 @@ def test_b017(self): filename = Path(__file__).absolute().parent / "b017.py" bbc = BugBearChecker(filename=str(filename)) errors = list(bbc.run()) - expected = self.errors(B017(22, 8)) + expected = self.errors(B017(24, 8), B017(26, 8)) self.assertEqual(errors, expected) def test_b018_functions(self):