blob: 1c9c55b126dcbe9dc37fbab13d263f5cf00f4e7e [file] [log] [blame]
Giampaolo Lauria1b837ce2013-05-01 11:22:07 -04001# Copyright 2013 IBM Corp.
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
Matthew Treinish662bc3c2014-04-07 17:55:39 -040015import os
Giampaolo Lauria1b837ce2013-05-01 11:22:07 -040016import re
17
Andreas Jaegerf27a3342020-03-29 10:21:39 +020018from hacking import core
Stephen Finucanefc42cc62018-07-06 13:39:55 +010019import pycodestyle
Matthew Treinishaaa35952014-05-02 18:50:16 -040020
Giampaolo Lauria1b837ce2013-05-01 11:22:07 -040021
Matthew Treinish7d710f92014-03-15 21:29:08 -040022PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
Shu Yingya44807072016-09-01 07:51:36 +080023 'ironic', 'heat', 'sahara']
Giampaolo Lauriab8424eb2013-05-23 15:56:21 -040024
Giampaolo Lauriab8424eb2013-05-23 15:56:21 -040025PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
Matthew Treinish6ba951a2013-09-09 22:06:18 +000026TEST_DEFINITION = re.compile(r'^\s*def test.*')
Andrea Frittoli41fa16d2014-09-15 13:41:37 +010027SETUP_TEARDOWN_CLASS_DEFINITION = re.compile(r'^\s+def (setUp|tearDown)Class')
Matthew Treinish662bc3c2014-04-07 17:55:39 -040028SCENARIO_DECORATOR = re.compile(r'\s*@.*services\((.*)\)')
Ken'ichi Ohmichi80369a92015-04-06 23:41:14 +000029RAND_NAME_HYPHEN_RE = re.compile(r".*rand_name\(.+[\-\_][\"\']\)")
Ghanshyam2a180b82014-06-16 13:54:22 +090030mutable_default_args = re.compile(r"^\s*def .+\((.+=\{\}|.+=\[\])")
John Warren3059a092015-08-31 15:34:49 -040031TESTTOOLS_SKIP_DECORATOR = re.compile(r'\s*@testtools\.skip\((.*)\)')
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +000032METHOD = re.compile(r"^ def .+")
33METHOD_GET_RESOURCE = re.compile(r"^\s*def (list|show)\_.+")
Ken'ichi Ohmichib8461cb2015-11-20 08:10:51 +000034METHOD_DELETE_RESOURCE = re.compile(r"^\s*def delete_.+")
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +000035CLASS = re.compile(r"^class .+")
junbolibc2ae862017-07-29 15:46:48 +080036EX_ATTRIBUTE = re.compile(r'(\s+|\()(e|ex|exc|exception).message(\s+|\))')
Felipe Monteiro4d011af2018-07-18 00:11:48 -040037NEGATIVE_TEST_DECORATOR = re.compile(
38 r'\s*@decorators\.attr\(type=.*negative.*\)')
39_HAVE_NEGATIVE_DECORATOR = False
Giampaolo Lauria1b837ce2013-05-01 11:22:07 -040040
41
Andreas Jaegerf27a3342020-03-29 10:21:39 +020042@core.flake8ext
ghanshyam50f19472014-11-26 17:04:37 +090043def import_no_clients_in_api_and_scenario_tests(physical_line, filename):
44 """Check for client imports from tempest/api & tempest/scenario tests
Giampaolo Lauria1b837ce2013-05-01 11:22:07 -040045
Giampaolo Lauriab8424eb2013-05-23 15:56:21 -040046 T102: Cannot import OpenStack python clients
47 """
Giampaolo Lauria1b837ce2013-05-01 11:22:07 -040048
ghanshyam50f19472014-11-26 17:04:37 +090049 if "tempest/api" in filename or "tempest/scenario" in filename:
Giampaolo Lauriab8424eb2013-05-23 15:56:21 -040050 res = PYTHON_CLIENT_RE.match(physical_line)
51 if res:
52 return (physical_line.find(res.group(1)),
53 ("T102: python clients import not allowed"
ghanshyam50f19472014-11-26 17:04:37 +090054 " in tempest/api/* or tempest/scenario/* tests"))
Giampaolo Lauriad50c27d2013-05-23 15:23:12 -040055
56
Andreas Jaegerf27a3342020-03-29 10:21:39 +020057@core.flake8ext
Matthew Treinish6ba951a2013-09-09 22:06:18 +000058def scenario_tests_need_service_tags(physical_line, filename,
59 previous_logical):
60 """Check that scenario tests have service tags
61
62 T104: Scenario tests require a services decorator
63 """
64
Matthew Treinishb12ad762014-06-19 10:18:05 -040065 if 'tempest/scenario/' in filename and '/test_' in filename:
Matthew Treinish6ba951a2013-09-09 22:06:18 +000066 if TEST_DEFINITION.match(physical_line):
67 if not SCENARIO_DECORATOR.match(previous_logical):
68 return (physical_line.find('def'),
69 "T104: Scenario tests require a service decorator")
70
71
Andreas Jaegerf27a3342020-03-29 10:21:39 +020072@core.flake8ext
Andrea Frittoli41fa16d2014-09-15 13:41:37 +010073def no_setup_teardown_class_for_tests(physical_line, filename):
Matthew Treinishaaa35952014-05-02 18:50:16 -040074
Stephen Finucanefc42cc62018-07-06 13:39:55 +010075 if pycodestyle.noqa(physical_line):
Matthew Treinishaaa35952014-05-02 18:50:16 -040076 return
77
Matthew Treinish9e26ca82016-02-23 11:43:20 -050078 if 'tempest/test.py' in filename or 'tempest/lib/' in filename:
79 return
80
81 if SETUP_TEARDOWN_CLASS_DEFINITION.match(physical_line):
82 return (physical_line.find('def'),
83 "T105: (setUp|tearDown)Class can not be used in tests")
Matthew Treinishecf212c2013-12-06 18:23:54 +000084
85
Andreas Jaegerf27a3342020-03-29 10:21:39 +020086@core.flake8ext
Matthew Treinish662bc3c2014-04-07 17:55:39 -040087def service_tags_not_in_module_path(physical_line, filename):
88 """Check that a service tag isn't in the module path
89
90 A service tag should only be added if the service name isn't already in
91 the module path.
92
93 T107
94 """
95 # NOTE(mtreinish) Scenario tests always need service tags, but subdirs are
96 # created for services like heat which would cause false negatives for
97 # those tests, so just exclude the scenario tests.
98 if 'tempest/scenario' not in filename:
99 matches = SCENARIO_DECORATOR.match(physical_line)
100 if matches:
101 services = matches.group(1).split(',')
102 for service in services:
103 service_name = service.strip().strip("'")
104 modulepath = os.path.split(filename)[0]
105 if service_name in modulepath:
106 return (physical_line.find(service_name),
107 "T107: service tag should not be in path")
108
109
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200110@core.flake8ext
Ken'ichi Ohmichi80369a92015-04-06 23:41:14 +0000111def no_hyphen_at_end_of_rand_name(logical_line, filename):
112 """Check no hyphen at the end of rand_name() argument
113
114 T108
115 """
Ken'ichi Ohmichi80369a92015-04-06 23:41:14 +0000116 msg = "T108: hyphen should not be specified at the end of rand_name()"
117 if RAND_NAME_HYPHEN_RE.match(logical_line):
118 return 0, msg
119
120
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200121@core.flake8ext
Ghanshyam2a180b82014-06-16 13:54:22 +0900122def no_mutable_default_args(logical_line):
123 """Check that mutable object isn't used as default argument
124
125 N322: Method's default argument shouldn't be mutable
126 """
127 msg = "N322: Method's default argument shouldn't be mutable!"
128 if mutable_default_args.match(logical_line):
129 yield (0, msg)
130
131
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200132@core.flake8ext
John Warren3059a092015-08-31 15:34:49 -0400133def no_testtools_skip_decorator(logical_line):
134 """Check that methods do not have the testtools.skip decorator
135
136 T109
137 """
138 if TESTTOOLS_SKIP_DECORATOR.match(logical_line):
139 yield (0, "T109: Cannot use testtools.skip decorator; instead use "
Andrea Frittoli (andreaf)1370baf2016-04-29 14:26:22 -0500140 "decorators.skip_because from tempest.lib")
John Warren3059a092015-08-31 15:34:49 -0400141
142
Ghanshyam Mann3d9a8692021-04-30 10:32:13 -0500143def _common_service_clients_check(logical_line, physical_line, filename):
Ken'ichi Ohmichi12b28e92016-04-06 10:43:51 -0700144 if not re.match('tempest/(lib/)?services/.*', filename):
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000145 return False
146
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000147 if not METHOD.match(physical_line):
148 return False
149
Stephen Finucanefc42cc62018-07-06 13:39:55 +0100150 if pycodestyle.noqa(physical_line):
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000151 return False
152
153 return True
154
155
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200156@core.flake8ext
157def get_resources_on_service_clients(physical_line, logical_line, filename,
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000158 line_number, lines):
159 """Check that service client names of GET should be consistent
160
161 T110
162 """
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000163 if not _common_service_clients_check(logical_line, physical_line,
Ghanshyam Mann3d9a8692021-04-30 10:32:13 -0500164 filename):
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000165 return
166
167 for line in lines[line_number:]:
168 if METHOD.match(line) or CLASS.match(line):
169 # the end of a method
170 return
171
Ken'ichi Ohmichif878e6e2016-01-13 05:10:17 +0000172 if 'self.get(' not in line and ('self.show_resource(' not in line and
173 'self.list_resources(' not in line):
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000174 continue
175
176 if METHOD_GET_RESOURCE.match(logical_line):
177 return
178
179 msg = ("T110: [GET /resources] methods should be list_<resource name>s"
180 " or show_<resource name>")
181 yield (0, msg)
182
183
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200184@core.flake8ext
185def delete_resources_on_service_clients(physical_line, logical_line, filename,
Ken'ichi Ohmichib8461cb2015-11-20 08:10:51 +0000186 line_number, lines):
187 """Check that service client names of DELETE should be consistent
188
189 T111
190 """
191 if not _common_service_clients_check(logical_line, physical_line,
Ghanshyam Mann3d9a8692021-04-30 10:32:13 -0500192 filename):
Ken'ichi Ohmichib8461cb2015-11-20 08:10:51 +0000193 return
194
195 for line in lines[line_number:]:
196 if METHOD.match(line) or CLASS.match(line):
197 # the end of a method
198 return
199
Ken'ichi Ohmichif878e6e2016-01-13 05:10:17 +0000200 if 'self.delete(' not in line and 'self.delete_resource(' not in line:
Ken'ichi Ohmichib8461cb2015-11-20 08:10:51 +0000201 continue
202
203 if METHOD_DELETE_RESOURCE.match(logical_line):
204 return
205
206 msg = ("T111: [DELETE /resources/<id>] methods should be "
207 "delete_<resource name>")
208 yield (0, msg)
209
210
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200211@core.flake8ext
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700212def dont_import_local_tempest_into_lib(logical_line, filename):
213 """Check that tempest.lib should not import local tempest code
214
215 T112
216 """
217 if 'tempest/lib/' not in filename:
218 return
219
Federico Ressi2d6bcaa2018-04-11 12:37:36 +0200220 if not ('from tempest' in logical_line or
221 'import tempest' in logical_line):
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700222 return
223
Federico Ressi2d6bcaa2018-04-11 12:37:36 +0200224 if ('from tempest.lib' in logical_line or
225 'import tempest.lib' in logical_line):
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700226 return
227
228 msg = ("T112: tempest.lib should not import local tempest code to avoid "
229 "circular dependency")
230 yield (0, msg)
231
232
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200233@core.flake8ext
Ken'ichi Ohmichid079c892016-04-19 11:23:36 -0700234def use_rand_uuid_instead_of_uuid4(logical_line, filename):
235 """Check that tests use data_utils.rand_uuid() instead of uuid.uuid4()
236
237 T113
238 """
239 if 'tempest/lib/' in filename:
240 return
241
242 if 'uuid.uuid4()' not in logical_line:
243 return
244
245 msg = ("T113: Tests should use data_utils.rand_uuid()/rand_uuid_hex() "
246 "instead of uuid.uuid4()/uuid.uuid4().hex")
247 yield (0, msg)
248
249
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200250@core.flake8ext
Matthew Treinish59d9eaa2016-05-31 23:42:55 -0400251def dont_use_config_in_tempest_lib(logical_line, filename):
252 """Check that tempest.lib doesn't use tempest config
253
254 T114
255 """
256
257 if 'tempest/lib/' not in filename:
258 return
259
Federico Ressi2d6bcaa2018-04-11 12:37:36 +0200260 if ('tempest.config' in logical_line or
261 'from tempest import config' in logical_line or
262 'oslo_config' in logical_line):
Matthew Treinish59d9eaa2016-05-31 23:42:55 -0400263 msg = ('T114: tempest.lib can not have any dependency on tempest '
264 'config.')
265 yield(0, msg)
266
267
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200268@core.flake8ext
269def dont_put_admin_tests_on_nonadmin_path(logical_line,
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700270 filename):
271 """Check admin tests should exist under admin path
272
273 T115
274 """
275
276 if 'tempest/api/' not in filename:
277 return
278
Stephen Finucane7f4a6212018-07-06 13:58:21 +0100279 if not re.match(r'class .*Test.*\(.*Admin.*\):', logical_line):
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700280 return
281
Stephen Finucane7f4a6212018-07-06 13:58:21 +0100282 if not re.match(r'.\/tempest\/api\/.*\/admin\/.*', filename):
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700283 msg = 'T115: All admin tests should exist under admin path.'
284 yield(0, msg)
285
286
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200287@core.flake8ext
junbolibc2ae862017-07-29 15:46:48 +0800288def unsupported_exception_attribute_PY3(logical_line):
289 """Check Unsupported 'message' exception attribute in PY3
290
291 T116
292 """
293 result = EX_ATTRIBUTE.search(logical_line)
294 msg = ("[T116] Unsupported 'message' Exception attribute in PY3")
295 if result:
296 yield(0, msg)
297
298
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200299@core.flake8ext
Felipe Monteiro4d011af2018-07-18 00:11:48 -0400300def negative_test_attribute_always_applied_to_negative_tests(physical_line,
301 filename):
302 """Check ``@decorators.attr(type=['negative'])`` applied to negative tests.
303
304 T117
305 """
306 global _HAVE_NEGATIVE_DECORATOR
307
308 if re.match(r'.\/tempest\/api\/.*_negative.*', filename):
309
310 if NEGATIVE_TEST_DECORATOR.match(physical_line):
311 _HAVE_NEGATIVE_DECORATOR = True
312 return
313
314 if TEST_DEFINITION.match(physical_line):
315 if not _HAVE_NEGATIVE_DECORATOR:
316 return (
317 0, "T117: Must apply `@decorators.attr(type=['negative'])`"
318 " to all negative API tests"
319 )
320 _HAVE_NEGATIVE_DECORATOR = False
Takashi Kajinami2a5ef1b2021-11-29 15:48:25 +0900321
322
323@core.flake8ext
324def no_log_warn(logical_line):
325 """Disallow 'LOG.warn('
326
327 Use LOG.warning() instead of Deprecated LOG.warn().
328 https://docs.python.org/3/library/logging.html#logging.warning
329 """
330
331 msg = ("T118: LOG.warn is deprecated, please use LOG.warning!")
332 if "LOG.warn(" in logical_line:
333 yield (0, msg)