test_flake8_plugin.py 5.4 KB

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