blob: 70089940b206b8a3bb8ade69cc638499a9c37d7f [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')
Michael Johnsona5074252020-06-30 10:28:09 -070069
70
71def _translation_checks_not_enforced(filename):
72 # Do not do these validations on tests
73 return any(pat in filename for pat in ["/tests/", "rally-jobs/plugins/"])
74
75
76@core.flake8ext
77def assert_true_instance(logical_line):
78 """Check for assertTrue(isinstance(a, b)) sentences
79
80 O316
81 """
82 if assert_trueinst_re.match(logical_line):
83 yield (0, "O316: assertTrue(isinstance(a, b)) sentences not allowed. "
84 "Use assertIsInstance instead.")
85
86
87@core.flake8ext
88def assert_equal_or_not_none(logical_line):
89 """Check for assertEqual(A, None) or assertEqual(None, A) sentences,
90
91 assertNotEqual(A, None) or assertNotEqual(None, A) sentences
92
93 O318
94 """
95 msg = ("O318: assertEqual/assertNotEqual(A, None) or "
96 "assertEqual/assertNotEqual(None, A) sentences not allowed")
97 res = (assert_equal_start_with_none_re.match(logical_line) or
98 assert_equal_end_with_none_re.match(logical_line) or
99 assert_not_equal_start_with_none_re.match(logical_line) or
100 assert_not_equal_end_with_none_re.match(logical_line))
101 if res:
102 yield (0, msg)
103
104
105@core.flake8ext
106def assert_equal_true_or_false(logical_line):
107 """Check for assertEqual(True, A) or assertEqual(False, A) sentences
108
109 O323
110 """
111 res = (assert_equal_with_true_re.search(logical_line) or
112 assert_equal_with_false_re.search(logical_line))
113 if res:
114 yield (0, "O323: assertEqual(True, A) or assertEqual(False, A) "
115 "sentences not allowed")
116
117
118@core.flake8ext
119def no_mutable_default_args(logical_line):
120 msg = "O324: Method's default argument shouldn't be mutable!"
121 if mutable_default_args.match(logical_line):
122 yield (0, msg)
123
124
125@core.flake8ext
126def assert_equal_in(logical_line):
127 """Check for assertEqual(A in B, True), assertEqual(True, A in B),
128
129 assertEqual(A in B, False) or assertEqual(False, A in B) sentences
130
131 O338
132 """
133 res = (assert_equal_in_start_with_true_or_false_re.search(logical_line) or
134 assert_equal_in_end_with_true_or_false_re.search(logical_line))
135 if res:
136 yield (0, "O338: Use assertIn/NotIn(A, B) rather than "
137 "assertEqual(A in B, True/False) when checking collection "
138 "contents.")
139
140
141@core.flake8ext
142def no_log_warn(logical_line):
143 """Disallow 'LOG.warn('
144
145 O339
146 """
147 if logical_line.startswith('LOG.warn('):
Michael Johnson77b8bae2024-11-08 01:39:29 +0000148 yield (0, "O339:Use LOG.warning() rather than LOG.warn()")
Michael Johnsona5074252020-06-30 10:28:09 -0700149
150
151@core.flake8ext
152def no_translate_logs(logical_line, filename):
153 """O341 - Don't translate logs.
154
155 Check for 'LOG.*(_(' and 'LOG.*(_Lx('
156
157 Translators don't provide translations for log messages, and operators
158 asked not to translate them.
159
160 * This check assumes that 'LOG' is a logger.
161
162 :param logical_line: The logical line to check.
163 :param filename: The file name where the logical line exists.
164 :returns: None if the logical line passes the check, otherwise a tuple
165 is yielded that contains the offending index in logical line
166 and a message describe the check validation failure.
167 """
168 if _translation_checks_not_enforced(filename):
169 return
170
171 msg = "O341: Log messages should not be translated!"
172 match = _log_translation_hint.match(logical_line)
173 if match:
174 yield (logical_line.index(match.group()), msg)
175
176
177@core.flake8ext
178def check_raised_localized_exceptions(logical_line, filename):
179 """O342 - Untranslated exception message.
180
181 :param logical_line: The logical line to check.
182 :param filename: The file name where the logical line exists.
183 :returns: None if the logical line passes the check, otherwise a tuple
184 is yielded that contains the offending index in logical line
185 and a message describe the check validation failure.
186 """
187 if _translation_checks_not_enforced(filename):
188 return
189
190 logical_line = logical_line.strip()
191 raised_search = untranslated_exception_re.match(logical_line)
192 if raised_search:
193 exception_msg = raised_search.groups()[0]
194 if exception_msg.startswith("\"") or exception_msg.startswith("\'"):
195 msg = "O342: Untranslated exception message."
196 yield (logical_line.index(exception_msg), msg)
197
198
199@core.flake8ext
200def check_no_eventlet_imports(logical_line):
201 """O345 - Usage of Python eventlet module not allowed.
202
203 :param logical_line: The logical line to check.
204 :returns: None if the logical line passes the check, otherwise a tuple
205 is yielded that contains the offending index in logical line
206 and a message describe the check validation failure.
207 """
208 if no_eventlet_re.match(logical_line):
209 msg = 'O345 Usage of Python eventlet module not allowed'
210 yield logical_line.index('eventlet'), msg
211
212
213@core.flake8ext
214def check_line_continuation_no_backslash(logical_line, tokens):
215 """O346 - Don't use backslashes for line continuation.
216
217 :param logical_line: The logical line to check. Not actually used.
218 :param tokens: List of tokens to check.
219 :returns: None if the tokens don't contain any issues, otherwise a tuple
220 is yielded that contains the offending index in the logical
221 line and a message describe the check validation failure.
222 """
223 backslash = None
224 for token_type, text, start, end, orig_line in tokens:
225 m = no_line_continuation_backslash_re.match(orig_line)
226 if m:
227 backslash = (start[0], m.start(1))
228 break
229
230 if backslash is not None:
231 msg = 'O346 Backslash line continuations not allowed'
232 yield backslash, msg
233
234
235@core.flake8ext
236def revert_must_have_kwargs(logical_line):
237 """O347 - Taskflow revert methods must have \\*\\*kwargs.
238
239 :param logical_line: The logical line to check.
240 :returns: None if the logical line passes the check, otherwise a tuple
241 is yielded that contains the offending index in logical line
242 and a message describe the check validation failure.
243 """
244 if revert_must_have_kwargs_re.match(logical_line):
245 msg = 'O347 Taskflow revert methods must have **kwargs'
246 yield 0, msg
247
248
249@core.flake8ext
250def check_no_logging_imports(logical_line):
251 """O348 - Usage of Python logging module not allowed.
252
253 :param logical_line: The logical line to check.
254 :returns: None if the logical line passes the check, otherwise a tuple
255 is yielded that contains the offending index in logical line
256 and a message describe the check validation failure.
257 """
258 if no_logging_re.match(logical_line):
259 msg = 'O348 Usage of Python logging module not allowed, use oslo_log'
260 yield logical_line.index('logging'), msg