test_flake8_plugin.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. from __future__ import annotations
  2. import ast
  3. import pytest
  4. from tools.flake8_plugin import SentryCheck
  5. def _run(src: str, filename: str = "getsentry/t.py") -> list[str]:
  6. tree = ast.parse(src)
  7. errors = sorted(SentryCheck(tree=tree, filename=filename).run())
  8. return ["t.py:{}:{}: {}".format(*error) for error in errors]
  9. def test_S001():
  10. S001_py = """\
  11. class A:
  12. def called_once():
  13. pass
  14. A().called_once()
  15. """
  16. errors = _run(S001_py)
  17. assert errors == [
  18. "t.py:6:0: S001 Avoid using the called_once mock call as it is confusing and "
  19. "prone to causing invalid test behavior.",
  20. ]
  21. def test_S002():
  22. S002_py = """\
  23. print("print statements are not allowed")
  24. """
  25. errors = _run(S002_py)
  26. assert errors == ["t.py:1:0: S002 print functions or statements are not allowed."]
  27. def test_S003():
  28. S003_py = """\
  29. import json
  30. import simplejson
  31. from json import loads, load
  32. from simplejson import JSONDecoder, JSONDecodeError, _default_encoder
  33. import sentry.utils.json as good_json
  34. from sentry.utils.json import JSONDecoder, JSONDecodeError
  35. from .json import Validator
  36. def bad_code():
  37. a = json.loads("''")
  38. b = simplejson.loads("''")
  39. c = loads("''")
  40. d = load()
  41. """
  42. errors = _run(S003_py)
  43. assert errors == [
  44. "t.py:1:0: S003 Use ``from sentry.utils import json`` instead.",
  45. "t.py:2:0: S003 Use ``from sentry.utils import json`` instead.",
  46. "t.py:3:0: S003 Use ``from sentry.utils import json`` instead.",
  47. "t.py:4:0: S003 Use ``from sentry.utils import json`` instead.",
  48. ]
  49. def test_S004():
  50. S004_py = """\
  51. import unittest
  52. from something import func
  53. class Test(unittest.TestCase):
  54. def test(self):
  55. with self.assertRaises(ValueError):
  56. func()
  57. """
  58. errors = _run(S004_py)
  59. assert errors == [
  60. "t.py:7:13: S004 Use `pytest.raises` instead for better debuggability.",
  61. ]
  62. def test_S005():
  63. S005_py = """\
  64. from sentry.models import User
  65. """
  66. errors = _run(S005_py)
  67. assert errors == [
  68. "t.py:1:0: S005 Do not import models from sentry.models but the actual module",
  69. ]
  70. def test_S006():
  71. src = """\
  72. from django.utils.encoding import force_bytes
  73. from django.utils.encoding import force_str
  74. """
  75. # only error in tests until we can fix the rest
  76. assert _run(src, filename="src/sentry/whatever.py") == []
  77. errors = _run(src, filename="tests/test_foo.py")
  78. assert errors == [
  79. "t.py:1:0: S006 Do not use force_bytes / force_str -- test the types directly",
  80. "t.py:2:0: S006 Do not use force_bytes / force_str -- test the types directly",
  81. ]
  82. def test_S007():
  83. src = """\
  84. from sentry.testutils.outbox import outbox_runner
  85. """
  86. # no errors in tests/
  87. assert _run(src, filename="tests/test_foo.py") == []
  88. # no errors in src/sentry/testutils/
  89. assert _run(src, filename="src/sentry/testutils/silo.py") == []
  90. # errors in other paths
  91. errors = _run(src, filename="src/sentry/api/endpoints/organization_details.py")
  92. assert errors == [
  93. "t.py:1:0: S007 Do not import sentry.testutils into production code.",
  94. ]
  95. # Module imports should have errors too.
  96. src = """\
  97. import sentry.testutils.outbox as outbox_utils
  98. """
  99. assert _run(src, filename="tests/test_foo.py") == []
  100. errors = _run(src, filename="src/sentry/api/endpoints/organization_details.py")
  101. assert errors == [
  102. "t.py:1:0: S007 Do not import sentry.testutils into production code.",
  103. ]
  104. @pytest.mark.parametrize(
  105. "src",
  106. (
  107. "from pytz import utc",
  108. "from pytz import UTC",
  109. "pytz.utc",
  110. "pytz.UTC",
  111. ),
  112. )
  113. def test_S008(src):
  114. expected = ["t.py:1:0: S008 Use stdlib datetime.timezone.utc instead of pytz.utc / pytz.UTC"]
  115. assert _run(src) == expected
  116. def test_S009():
  117. src = """\
  118. try:
  119. ...
  120. except OSError:
  121. raise # ok: what we want people to do!
  122. except TypeError as e:
  123. raise RuntimeError() # ok: reraising a different exception
  124. except ValueError as e:
  125. raise e # bad!
  126. """
  127. expected = ["t.py:8:4: S009 Use `raise` with no arguments to reraise exceptions"]
  128. assert _run(src) == expected
  129. def test_S010():
  130. src = """\
  131. try:
  132. ...
  133. except ValueError:
  134. ... # ok: not a reraise body
  135. except Exception:
  136. raise # bad!
  137. try:
  138. ...
  139. except Exception:
  140. ...
  141. raise # ok: non just a reraise body
  142. """
  143. expected = ["t.py:5:0: S010 Except handler does nothing and should be removed"]
  144. assert _run(src) == expected
  145. def test_S011():
  146. src = """\
  147. from sentry.testutils.cases import APITestCase
  148. from django.test import override_settings
  149. def test():
  150. with override_settings(SENTRY_OPTIONS={"foo": "bar"}): # bad
  151. ...
  152. with override_settings(
  153. SENTRY_OPTIONS={"foo": "bar"}, # bad
  154. OTHER_SETTING=2, # ok
  155. ):
  156. ...
  157. with override_settings(OTHER_SETTING=2): # ok
  158. ...
  159. class Test(ApiTestCase):
  160. def test(self):
  161. with self.settings(SENTRY_OPTIONS={"foo": "bar"}): # bad
  162. ...
  163. """
  164. expected = [
  165. "t.py:5:27: S011 Use override_options(...) instead to ensure proper cleanup",
  166. "t.py:9:8: S011 Use override_options(...) instead to ensure proper cleanup",
  167. "t.py:19:27: S011 Use override_options(...) instead to ensure proper cleanup",
  168. ]
  169. assert _run(src, filename="tests/test_example.py") == expected