blob: 6a97a004a7c418b0d32aaf5764a1c6b24a50f26a [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
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000143def _common_service_clients_check(logical_line, physical_line, filename,
144 ignored_list_file=None):
Ken'ichi Ohmichi12b28e92016-04-06 10:43:51 -0700145 if not re.match('tempest/(lib/)?services/.*', filename):
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000146 return False
147
148 if ignored_list_file is not None:
149 ignored_list = []
150 with open('tempest/hacking/' + ignored_list_file) as f:
151 for line in f:
152 ignored_list.append(line.strip())
153
154 if filename in ignored_list:
155 return False
156
157 if not METHOD.match(physical_line):
158 return False
159
Stephen Finucanefc42cc62018-07-06 13:39:55 +0100160 if pycodestyle.noqa(physical_line):
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000161 return False
162
163 return True
164
165
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200166@core.flake8ext
167def get_resources_on_service_clients(physical_line, logical_line, filename,
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000168 line_number, lines):
169 """Check that service client names of GET should be consistent
170
171 T110
172 """
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000173 if not _common_service_clients_check(logical_line, physical_line,
174 filename, 'ignored_list_T110.txt'):
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000175 return
176
177 for line in lines[line_number:]:
178 if METHOD.match(line) or CLASS.match(line):
179 # the end of a method
180 return
181
Ken'ichi Ohmichif878e6e2016-01-13 05:10:17 +0000182 if 'self.get(' not in line and ('self.show_resource(' not in line and
183 'self.list_resources(' not in line):
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000184 continue
185
186 if METHOD_GET_RESOURCE.match(logical_line):
187 return
188
189 msg = ("T110: [GET /resources] methods should be list_<resource name>s"
190 " or show_<resource name>")
191 yield (0, msg)
192
193
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200194@core.flake8ext
195def delete_resources_on_service_clients(physical_line, logical_line, filename,
Ken'ichi Ohmichib8461cb2015-11-20 08:10:51 +0000196 line_number, lines):
197 """Check that service client names of DELETE should be consistent
198
199 T111
200 """
201 if not _common_service_clients_check(logical_line, physical_line,
202 filename, 'ignored_list_T111.txt'):
203 return
204
205 for line in lines[line_number:]:
206 if METHOD.match(line) or CLASS.match(line):
207 # the end of a method
208 return
209
Ken'ichi Ohmichif878e6e2016-01-13 05:10:17 +0000210 if 'self.delete(' not in line and 'self.delete_resource(' not in line:
Ken'ichi Ohmichib8461cb2015-11-20 08:10:51 +0000211 continue
212
213 if METHOD_DELETE_RESOURCE.match(logical_line):
214 return
215
216 msg = ("T111: [DELETE /resources/<id>] methods should be "
217 "delete_<resource name>")
218 yield (0, msg)
219
220
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200221@core.flake8ext
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700222def dont_import_local_tempest_into_lib(logical_line, filename):
223 """Check that tempest.lib should not import local tempest code
224
225 T112
226 """
227 if 'tempest/lib/' not in filename:
228 return
229
Federico Ressi2d6bcaa2018-04-11 12:37:36 +0200230 if not ('from tempest' in logical_line or
231 'import tempest' in logical_line):
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700232 return
233
Federico Ressi2d6bcaa2018-04-11 12:37:36 +0200234 if ('from tempest.lib' in logical_line or
235 'import tempest.lib' in logical_line):
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700236 return
237
238 msg = ("T112: tempest.lib should not import local tempest code to avoid "
239 "circular dependency")
240 yield (0, msg)
241
242
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200243@core.flake8ext
Ken'ichi Ohmichid079c892016-04-19 11:23:36 -0700244def use_rand_uuid_instead_of_uuid4(logical_line, filename):
245 """Check that tests use data_utils.rand_uuid() instead of uuid.uuid4()
246
247 T113
248 """
249 if 'tempest/lib/' in filename:
250 return
251
252 if 'uuid.uuid4()' not in logical_line:
253 return
254
255 msg = ("T113: Tests should use data_utils.rand_uuid()/rand_uuid_hex() "
256 "instead of uuid.uuid4()/uuid.uuid4().hex")
257 yield (0, msg)
258
259
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200260@core.flake8ext
Matthew Treinish59d9eaa2016-05-31 23:42:55 -0400261def dont_use_config_in_tempest_lib(logical_line, filename):
262 """Check that tempest.lib doesn't use tempest config
263
264 T114
265 """
266
267 if 'tempest/lib/' not in filename:
268 return
269
Federico Ressi2d6bcaa2018-04-11 12:37:36 +0200270 if ('tempest.config' in logical_line or
271 'from tempest import config' in logical_line or
272 'oslo_config' in logical_line):
Matthew Treinish59d9eaa2016-05-31 23:42:55 -0400273 msg = ('T114: tempest.lib can not have any dependency on tempest '
274 'config.')
275 yield(0, msg)
276
277
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200278@core.flake8ext
279def dont_put_admin_tests_on_nonadmin_path(logical_line,
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700280 filename):
281 """Check admin tests should exist under admin path
282
283 T115
284 """
285
286 if 'tempest/api/' not in filename:
287 return
288
Stephen Finucane7f4a6212018-07-06 13:58:21 +0100289 if not re.match(r'class .*Test.*\(.*Admin.*\):', logical_line):
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700290 return
291
Stephen Finucane7f4a6212018-07-06 13:58:21 +0100292 if not re.match(r'.\/tempest\/api\/.*\/admin\/.*', filename):
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700293 msg = 'T115: All admin tests should exist under admin path.'
294 yield(0, msg)
295
296
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200297@core.flake8ext
junbolibc2ae862017-07-29 15:46:48 +0800298def unsupported_exception_attribute_PY3(logical_line):
299 """Check Unsupported 'message' exception attribute in PY3
300
301 T116
302 """
303 result = EX_ATTRIBUTE.search(logical_line)
304 msg = ("[T116] Unsupported 'message' Exception attribute in PY3")
305 if result:
306 yield(0, msg)
307
308
Andreas Jaegerf27a3342020-03-29 10:21:39 +0200309@core.flake8ext
Felipe Monteiro4d011af2018-07-18 00:11:48 -0400310def negative_test_attribute_always_applied_to_negative_tests(physical_line,
311 filename):
312 """Check ``@decorators.attr(type=['negative'])`` applied to negative tests.
313
314 T117
315 """
316 global _HAVE_NEGATIVE_DECORATOR
317
318 if re.match(r'.\/tempest\/api\/.*_negative.*', filename):
319
320 if NEGATIVE_TEST_DECORATOR.match(physical_line):
321 _HAVE_NEGATIVE_DECORATOR = True
322 return
323
324 if TEST_DEFINITION.match(physical_line):
325 if not _HAVE_NEGATIVE_DECORATOR:
326 return (
327 0, "T117: Must apply `@decorators.attr(type=['negative'])`"
328 " to all negative API tests"
329 )
330 _HAVE_NEGATIVE_DECORATOR = False