blob: eec747609bbc97e30fcfcd796acca3432b062e90 [file] [log] [blame]
Michael Johnsona5074252020-06-30 10:28:09 -07001# Copyright (c) 2014 OpenStack Foundation.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15
16"""
17Guidelines for writing new hacking checks
18
19 - Use only for Octavia specific tests. OpenStack general tests
20 should be submitted to the common 'hacking' module.
21 - Pick numbers in the range O3xx. Find the current test with
22 the highest allocated number and then pick the next value.
23 - Keep the test method code in the source file ordered based
24 on the O3xx value.
25 - List the new rule in the top level HACKING.rst file
26 - Add test cases for each new rule to
27 octavia/tests/unit/test_hacking.py
28
29"""
30
31import re
32
33from hacking import core
34
35
36_all_log_levels = {'critical', 'error', 'exception', 'info', 'warning'}
37_all_hints = {'_LC', '_LE', '_LI', '_', '_LW'}
38
39_log_translation_hint = re.compile(
40 r".*LOG\.(%(levels)s)\(\s*(%(hints)s)\(" % {
41 'levels': '|'.join(_all_log_levels),
42 'hints': '|'.join(_all_hints),
43 })
44
45assert_trueinst_re = re.compile(
46 r"(.)*assertTrue\(isinstance\((\w|\.|\'|\"|\[|\])+, "
47 r"(\w|\.|\'|\"|\[|\])+\)\)")
48assert_equal_in_end_with_true_or_false_re = re.compile(
49 r"assertEqual\((\w|[][.'\"])+ in (\w|[][.'\", ])+, (True|False)\)")
50assert_equal_in_start_with_true_or_false_re = re.compile(
51 r"assertEqual\((True|False), (\w|[][.'\"])+ in (\w|[][.'\", ])+\)")
52assert_equal_with_true_re = re.compile(
53 r"assertEqual\(True,")
54assert_equal_with_false_re = re.compile(
55 r"assertEqual\(False,")
56mutable_default_args = re.compile(r"^\s*def .+\((.+=\{\}|.+=\[\])")
57assert_equal_end_with_none_re = re.compile(r"(.)*assertEqual\(.+, None\)")
58assert_equal_start_with_none_re = re.compile(r".*assertEqual\(None, .+\)")
59assert_not_equal_end_with_none_re = re.compile(
60 r"(.)*assertNotEqual\(.+, None\)")
61assert_not_equal_start_with_none_re = re.compile(
62 r"(.)*assertNotEqual\(None, .+\)")
63revert_must_have_kwargs_re = re.compile(
64 r'[ ]*def revert\(.+,[ ](?!\*\*kwargs)\w+\):')
65untranslated_exception_re = re.compile(r"raise (?:\w*)\((.*)\)")
66no_eventlet_re = re.compile(r'(import|from)\s+[(]?eventlet')
67no_line_continuation_backslash_re = re.compile(r'.*(\\)\n')
68no_logging_re = re.compile(r'(import|from)\s+[(]?logging')
69import_mock_re = re.compile(r"\bimport[\s]+mock\b")
70import_from_mock_re = re.compile(r"\bfrom[\s]+mock[\s]+import\b")
71
72
73def _translation_checks_not_enforced(filename):
74 # Do not do these validations on tests
75 return any(pat in filename for pat in ["/tests/", "rally-jobs/plugins/"])
76
77
78@core.flake8ext
79def assert_true_instance(logical_line):
80 """Check for assertTrue(isinstance(a, b)) sentences
81
82 O316
83 """
84 if assert_trueinst_re.match(logical_line):
85 yield (0, "O316: assertTrue(isinstance(a, b)) sentences not allowed. "
86 "Use assertIsInstance instead.")
87
88
89@core.flake8ext
90def assert_equal_or_not_none(logical_line):
91 """Check for assertEqual(A, None) or assertEqual(None, A) sentences,
92
93 assertNotEqual(A, None) or assertNotEqual(None, A) sentences
94
95 O318
96 """
97 msg = ("O318: assertEqual/assertNotEqual(A, None) or "
98 "assertEqual/assertNotEqual(None, A) sentences not allowed")
99 res = (assert_equal_start_with_none_re.match(logical_line) or
100 assert_equal_end_with_none_re.match(logical_line) or
101 assert_not_equal_start_with_none_re.match(logical_line) or
102 assert_not_equal_end_with_none_re.match(logical_line))
103 if res:
104 yield (0, msg)
105
106
107@core.flake8ext
108def assert_equal_true_or_false(logical_line):
109 """Check for assertEqual(True, A) or assertEqual(False, A) sentences
110
111 O323
112 """
113 res = (assert_equal_with_true_re.search(logical_line) or
114 assert_equal_with_false_re.search(logical_line))
115 if res:
116 yield (0, "O323: assertEqual(True, A) or assertEqual(False, A) "
117 "sentences not allowed")
118
119
120@core.flake8ext
121def no_mutable_default_args(logical_line):
122 msg = "O324: Method's default argument shouldn't be mutable!"
123 if mutable_default_args.match(logical_line):
124 yield (0, msg)
125
126
127@core.flake8ext
128def assert_equal_in(logical_line):
129 """Check for assertEqual(A in B, True), assertEqual(True, A in B),
130
131 assertEqual(A in B, False) or assertEqual(False, A in B) sentences
132
133 O338
134 """
135 res = (assert_equal_in_start_with_true_or_false_re.search(logical_line) or
136 assert_equal_in_end_with_true_or_false_re.search(logical_line))
137 if res:
138 yield (0, "O338: Use assertIn/NotIn(A, B) rather than "
139 "assertEqual(A in B, True/False) when checking collection "
140 "contents.")
141
142
143@core.flake8ext
144def no_log_warn(logical_line):
145 """Disallow 'LOG.warn('
146
147 O339
148 """
149 if logical_line.startswith('LOG.warn('):
150 yield(0, "O339:Use LOG.warning() rather than LOG.warn()")
151
152
153@core.flake8ext
154def no_translate_logs(logical_line, filename):
155 """O341 - Don't translate logs.
156
157 Check for 'LOG.*(_(' and 'LOG.*(_Lx('
158
159 Translators don't provide translations for log messages, and operators
160 asked not to translate them.
161
162 * This check assumes that 'LOG' is a logger.
163
164 :param logical_line: The logical line to check.
165 :param filename: The file name where the logical line exists.
166 :returns: None if the logical line passes the check, otherwise a tuple
167 is yielded that contains the offending index in logical line
168 and a message describe the check validation failure.
169 """
170 if _translation_checks_not_enforced(filename):
171 return
172
173 msg = "O341: Log messages should not be translated!"
174 match = _log_translation_hint.match(logical_line)
175 if match:
176 yield (logical_line.index(match.group()), msg)
177
178
179@core.flake8ext
180def check_raised_localized_exceptions(logical_line, filename):
181 """O342 - Untranslated exception message.
182
183 :param logical_line: The logical line to check.
184 :param filename: The file name where the logical line exists.
185 :returns: None if the logical line passes the check, otherwise a tuple
186 is yielded that contains the offending index in logical line
187 and a message describe the check validation failure.
188 """
189 if _translation_checks_not_enforced(filename):
190 return
191
192 logical_line = logical_line.strip()
193 raised_search = untranslated_exception_re.match(logical_line)
194 if raised_search:
195 exception_msg = raised_search.groups()[0]
196 if exception_msg.startswith("\"") or exception_msg.startswith("\'"):
197 msg = "O342: Untranslated exception message."
198 yield (logical_line.index(exception_msg), msg)
199
200
201@core.flake8ext
202def check_no_eventlet_imports(logical_line):
203 """O345 - Usage of Python eventlet module not allowed.
204
205 :param logical_line: The logical line to check.
206 :returns: None if the logical line passes the check, otherwise a tuple
207 is yielded that contains the offending index in logical line
208 and a message describe the check validation failure.
209 """
210 if no_eventlet_re.match(logical_line):
211 msg = 'O345 Usage of Python eventlet module not allowed'
212 yield logical_line.index('eventlet'), msg
213
214
215@core.flake8ext
216def check_line_continuation_no_backslash(logical_line, tokens):
217 """O346 - Don't use backslashes for line continuation.
218
219 :param logical_line: The logical line to check. Not actually used.
220 :param tokens: List of tokens to check.
221 :returns: None if the tokens don't contain any issues, otherwise a tuple
222 is yielded that contains the offending index in the logical
223 line and a message describe the check validation failure.
224 """
225 backslash = None
226 for token_type, text, start, end, orig_line in tokens:
227 m = no_line_continuation_backslash_re.match(orig_line)
228 if m:
229 backslash = (start[0], m.start(1))
230 break
231
232 if backslash is not None:
233 msg = 'O346 Backslash line continuations not allowed'
234 yield backslash, msg
235
236
237@core.flake8ext
238def revert_must_have_kwargs(logical_line):
239 """O347 - Taskflow revert methods must have \\*\\*kwargs.
240
241 :param logical_line: The logical line to check.
242 :returns: None if the logical line passes the check, otherwise a tuple
243 is yielded that contains the offending index in logical line
244 and a message describe the check validation failure.
245 """
246 if revert_must_have_kwargs_re.match(logical_line):
247 msg = 'O347 Taskflow revert methods must have **kwargs'
248 yield 0, msg
249
250
251@core.flake8ext
252def check_no_logging_imports(logical_line):
253 """O348 - Usage of Python logging module not allowed.
254
255 :param logical_line: The logical line to check.
256 :returns: None if the logical line passes the check, otherwise a tuple
257 is yielded that contains the offending index in logical line
258 and a message describe the check validation failure.
259 """
260 if no_logging_re.match(logical_line):
261 msg = 'O348 Usage of Python logging module not allowed, use oslo_log'
262 yield logical_line.index('logging'), msg
263
264
265@core.flake8ext
266def check_no_import_mock(logical_line):
267 """O349 - Test code must not import mock library.
268
269 :param logical_line: The logical line to check.
270 :returns: None if the logical line passes the check, otherwise a tuple
271 is yielded that contains the offending index in logical line
272 and a message describe the check validation failure.
273 """
274 if (import_mock_re.match(logical_line) or
275 import_from_mock_re.match(logical_line)):
276 msg = 'O349 Test code must not import mock library, use unittest.mock'
277 yield 0, msg