blob: 2c40cb1871c0b8c752ac38e088c73500f05086e1 [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
Stephen Finucanefc42cc62018-07-06 13:39:55 +010018import pycodestyle
Matthew Treinishaaa35952014-05-02 18:50:16 -040019
Giampaolo Lauria1b837ce2013-05-01 11:22:07 -040020
Matthew Treinish7d710f92014-03-15 21:29:08 -040021PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
Shu Yingya44807072016-09-01 07:51:36 +080022 'ironic', 'heat', 'sahara']
Giampaolo Lauriab8424eb2013-05-23 15:56:21 -040023
Giampaolo Lauriab8424eb2013-05-23 15:56:21 -040024PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
Matthew Treinish6ba951a2013-09-09 22:06:18 +000025TEST_DEFINITION = re.compile(r'^\s*def test.*')
Andrea Frittoli41fa16d2014-09-15 13:41:37 +010026SETUP_TEARDOWN_CLASS_DEFINITION = re.compile(r'^\s+def (setUp|tearDown)Class')
Matthew Treinish662bc3c2014-04-07 17:55:39 -040027SCENARIO_DECORATOR = re.compile(r'\s*@.*services\((.*)\)')
Masayuki Igawafcacf962014-02-19 14:00:01 +090028VI_HEADER_RE = re.compile(r"^#\s+vim?:.+")
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
ghanshyam50f19472014-11-26 17:04:37 +090042def import_no_clients_in_api_and_scenario_tests(physical_line, filename):
43 """Check for client imports from tempest/api & tempest/scenario tests
Giampaolo Lauria1b837ce2013-05-01 11:22:07 -040044
Giampaolo Lauriab8424eb2013-05-23 15:56:21 -040045 T102: Cannot import OpenStack python clients
46 """
Giampaolo Lauria1b837ce2013-05-01 11:22:07 -040047
ghanshyam50f19472014-11-26 17:04:37 +090048 if "tempest/api" in filename or "tempest/scenario" in filename:
Giampaolo Lauriab8424eb2013-05-23 15:56:21 -040049 res = PYTHON_CLIENT_RE.match(physical_line)
50 if res:
51 return (physical_line.find(res.group(1)),
52 ("T102: python clients import not allowed"
ghanshyam50f19472014-11-26 17:04:37 +090053 " in tempest/api/* or tempest/scenario/* tests"))
Giampaolo Lauriad50c27d2013-05-23 15:23:12 -040054
55
Matthew Treinish6ba951a2013-09-09 22:06:18 +000056def scenario_tests_need_service_tags(physical_line, filename,
57 previous_logical):
58 """Check that scenario tests have service tags
59
60 T104: Scenario tests require a services decorator
61 """
62
Matthew Treinishb12ad762014-06-19 10:18:05 -040063 if 'tempest/scenario/' in filename and '/test_' in filename:
Matthew Treinish6ba951a2013-09-09 22:06:18 +000064 if TEST_DEFINITION.match(physical_line):
65 if not SCENARIO_DECORATOR.match(previous_logical):
66 return (physical_line.find('def'),
67 "T104: Scenario tests require a service decorator")
68
69
Andrea Frittoli41fa16d2014-09-15 13:41:37 +010070def no_setup_teardown_class_for_tests(physical_line, filename):
Matthew Treinishaaa35952014-05-02 18:50:16 -040071
Stephen Finucanefc42cc62018-07-06 13:39:55 +010072 if pycodestyle.noqa(physical_line):
Matthew Treinishaaa35952014-05-02 18:50:16 -040073 return
74
Matthew Treinish9e26ca82016-02-23 11:43:20 -050075 if 'tempest/test.py' in filename or 'tempest/lib/' in filename:
76 return
77
78 if SETUP_TEARDOWN_CLASS_DEFINITION.match(physical_line):
79 return (physical_line.find('def'),
80 "T105: (setUp|tearDown)Class can not be used in tests")
Matthew Treinishecf212c2013-12-06 18:23:54 +000081
82
Masayuki Igawafcacf962014-02-19 14:00:01 +090083def no_vi_headers(physical_line, line_number, lines):
84 """Check for vi editor configuration in source files.
85
86 By default vi modelines can only appear in the first or
87 last 5 lines of a source file.
88
89 T106
90 """
91 # NOTE(gilliard): line_number is 1-indexed
92 if line_number <= 5 or line_number > len(lines) - 5:
93 if VI_HEADER_RE.match(physical_line):
94 return 0, "T106: Don't put vi configuration in source files"
95
96
Matthew Treinish662bc3c2014-04-07 17:55:39 -040097def service_tags_not_in_module_path(physical_line, filename):
98 """Check that a service tag isn't in the module path
99
100 A service tag should only be added if the service name isn't already in
101 the module path.
102
103 T107
104 """
105 # NOTE(mtreinish) Scenario tests always need service tags, but subdirs are
106 # created for services like heat which would cause false negatives for
107 # those tests, so just exclude the scenario tests.
108 if 'tempest/scenario' not in filename:
109 matches = SCENARIO_DECORATOR.match(physical_line)
110 if matches:
111 services = matches.group(1).split(',')
112 for service in services:
113 service_name = service.strip().strip("'")
114 modulepath = os.path.split(filename)[0]
115 if service_name in modulepath:
116 return (physical_line.find(service_name),
117 "T107: service tag should not be in path")
118
119
Ken'ichi Ohmichi80369a92015-04-06 23:41:14 +0000120def no_hyphen_at_end_of_rand_name(logical_line, filename):
121 """Check no hyphen at the end of rand_name() argument
122
123 T108
124 """
Ken'ichi Ohmichi80369a92015-04-06 23:41:14 +0000125 msg = "T108: hyphen should not be specified at the end of rand_name()"
126 if RAND_NAME_HYPHEN_RE.match(logical_line):
127 return 0, msg
128
129
Ghanshyam2a180b82014-06-16 13:54:22 +0900130def no_mutable_default_args(logical_line):
131 """Check that mutable object isn't used as default argument
132
133 N322: Method's default argument shouldn't be mutable
134 """
135 msg = "N322: Method's default argument shouldn't be mutable!"
136 if mutable_default_args.match(logical_line):
137 yield (0, msg)
138
139
John Warren3059a092015-08-31 15:34:49 -0400140def no_testtools_skip_decorator(logical_line):
141 """Check that methods do not have the testtools.skip decorator
142
143 T109
144 """
145 if TESTTOOLS_SKIP_DECORATOR.match(logical_line):
146 yield (0, "T109: Cannot use testtools.skip decorator; instead use "
Andrea Frittoli (andreaf)1370baf2016-04-29 14:26:22 -0500147 "decorators.skip_because from tempest.lib")
John Warren3059a092015-08-31 15:34:49 -0400148
149
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000150def _common_service_clients_check(logical_line, physical_line, filename,
151 ignored_list_file=None):
Ken'ichi Ohmichi12b28e92016-04-06 10:43:51 -0700152 if not re.match('tempest/(lib/)?services/.*', filename):
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000153 return False
154
155 if ignored_list_file is not None:
156 ignored_list = []
157 with open('tempest/hacking/' + ignored_list_file) as f:
158 for line in f:
159 ignored_list.append(line.strip())
160
161 if filename in ignored_list:
162 return False
163
164 if not METHOD.match(physical_line):
165 return False
166
Stephen Finucanefc42cc62018-07-06 13:39:55 +0100167 if pycodestyle.noqa(physical_line):
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000168 return False
169
170 return True
171
172
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000173def get_resources_on_service_clients(logical_line, physical_line, filename,
174 line_number, lines):
175 """Check that service client names of GET should be consistent
176
177 T110
178 """
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000179 if not _common_service_clients_check(logical_line, physical_line,
180 filename, 'ignored_list_T110.txt'):
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000181 return
182
183 for line in lines[line_number:]:
184 if METHOD.match(line) or CLASS.match(line):
185 # the end of a method
186 return
187
Ken'ichi Ohmichif878e6e2016-01-13 05:10:17 +0000188 if 'self.get(' not in line and ('self.show_resource(' not in line and
189 'self.list_resources(' not in line):
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000190 continue
191
192 if METHOD_GET_RESOURCE.match(logical_line):
193 return
194
195 msg = ("T110: [GET /resources] methods should be list_<resource name>s"
196 " or show_<resource name>")
197 yield (0, msg)
198
199
Ken'ichi Ohmichib8461cb2015-11-20 08:10:51 +0000200def delete_resources_on_service_clients(logical_line, physical_line, filename,
201 line_number, lines):
202 """Check that service client names of DELETE should be consistent
203
204 T111
205 """
206 if not _common_service_clients_check(logical_line, physical_line,
207 filename, 'ignored_list_T111.txt'):
208 return
209
210 for line in lines[line_number:]:
211 if METHOD.match(line) or CLASS.match(line):
212 # the end of a method
213 return
214
Ken'ichi Ohmichif878e6e2016-01-13 05:10:17 +0000215 if 'self.delete(' not in line and 'self.delete_resource(' not in line:
Ken'ichi Ohmichib8461cb2015-11-20 08:10:51 +0000216 continue
217
218 if METHOD_DELETE_RESOURCE.match(logical_line):
219 return
220
221 msg = ("T111: [DELETE /resources/<id>] methods should be "
222 "delete_<resource name>")
223 yield (0, msg)
224
225
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700226def dont_import_local_tempest_into_lib(logical_line, filename):
227 """Check that tempest.lib should not import local tempest code
228
229 T112
230 """
231 if 'tempest/lib/' not in filename:
232 return
233
Federico Ressi2d6bcaa2018-04-11 12:37:36 +0200234 if not ('from tempest' in logical_line or
235 'import tempest' in logical_line):
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700236 return
237
Federico Ressi2d6bcaa2018-04-11 12:37:36 +0200238 if ('from tempest.lib' in logical_line or
239 'import tempest.lib' in logical_line):
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700240 return
241
242 msg = ("T112: tempest.lib should not import local tempest code to avoid "
243 "circular dependency")
244 yield (0, msg)
245
246
Ken'ichi Ohmichid079c892016-04-19 11:23:36 -0700247def use_rand_uuid_instead_of_uuid4(logical_line, filename):
248 """Check that tests use data_utils.rand_uuid() instead of uuid.uuid4()
249
250 T113
251 """
252 if 'tempest/lib/' in filename:
253 return
254
255 if 'uuid.uuid4()' not in logical_line:
256 return
257
258 msg = ("T113: Tests should use data_utils.rand_uuid()/rand_uuid_hex() "
259 "instead of uuid.uuid4()/uuid.uuid4().hex")
260 yield (0, msg)
261
262
Matthew Treinish59d9eaa2016-05-31 23:42:55 -0400263def dont_use_config_in_tempest_lib(logical_line, filename):
264 """Check that tempest.lib doesn't use tempest config
265
266 T114
267 """
268
269 if 'tempest/lib/' not in filename:
270 return
271
Federico Ressi2d6bcaa2018-04-11 12:37:36 +0200272 if ('tempest.config' in logical_line or
273 'from tempest import config' in logical_line or
274 'oslo_config' in logical_line):
Matthew Treinish59d9eaa2016-05-31 23:42:55 -0400275 msg = ('T114: tempest.lib can not have any dependency on tempest '
276 'config.')
277 yield(0, msg)
278
279
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700280def dont_put_admin_tests_on_nonadmin_path(logical_line, physical_line,
281 filename):
282 """Check admin tests should exist under admin path
283
284 T115
285 """
286
287 if 'tempest/api/' not in filename:
288 return
289
Stephen Finucanefc42cc62018-07-06 13:39:55 +0100290 if pycodestyle.noqa(physical_line):
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700291 return
292
Stephen Finucane7f4a6212018-07-06 13:58:21 +0100293 if not re.match(r'class .*Test.*\(.*Admin.*\):', logical_line):
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700294 return
295
Stephen Finucane7f4a6212018-07-06 13:58:21 +0100296 if not re.match(r'.\/tempest\/api\/.*\/admin\/.*', filename):
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700297 msg = 'T115: All admin tests should exist under admin path.'
298 yield(0, msg)
299
300
junbolibc2ae862017-07-29 15:46:48 +0800301def unsupported_exception_attribute_PY3(logical_line):
302 """Check Unsupported 'message' exception attribute in PY3
303
304 T116
305 """
306 result = EX_ATTRIBUTE.search(logical_line)
307 msg = ("[T116] Unsupported 'message' Exception attribute in PY3")
308 if result:
309 yield(0, msg)
310
311
Felipe Monteiro4d011af2018-07-18 00:11:48 -0400312def negative_test_attribute_always_applied_to_negative_tests(physical_line,
313 filename):
314 """Check ``@decorators.attr(type=['negative'])`` applied to negative tests.
315
316 T117
317 """
318 global _HAVE_NEGATIVE_DECORATOR
319
320 if re.match(r'.\/tempest\/api\/.*_negative.*', filename):
321
322 if NEGATIVE_TEST_DECORATOR.match(physical_line):
323 _HAVE_NEGATIVE_DECORATOR = True
324 return
325
326 if TEST_DEFINITION.match(physical_line):
327 if not _HAVE_NEGATIVE_DECORATOR:
328 return (
329 0, "T117: Must apply `@decorators.attr(type=['negative'])`"
330 " to all negative API tests"
331 )
332 _HAVE_NEGATIVE_DECORATOR = False
333
334
Giampaolo Lauriad50c27d2013-05-23 15:23:12 -0400335def factory(register):
ghanshyam50f19472014-11-26 17:04:37 +0900336 register(import_no_clients_in_api_and_scenario_tests)
Matthew Treinish6ba951a2013-09-09 22:06:18 +0000337 register(scenario_tests_need_service_tags)
Andrea Frittoli41fa16d2014-09-15 13:41:37 +0100338 register(no_setup_teardown_class_for_tests)
Masayuki Igawafcacf962014-02-19 14:00:01 +0900339 register(no_vi_headers)
Matthew Treinish662bc3c2014-04-07 17:55:39 -0400340 register(service_tags_not_in_module_path)
Ken'ichi Ohmichi80369a92015-04-06 23:41:14 +0000341 register(no_hyphen_at_end_of_rand_name)
Ghanshyam2a180b82014-06-16 13:54:22 +0900342 register(no_mutable_default_args)
John Warren3059a092015-08-31 15:34:49 -0400343 register(no_testtools_skip_decorator)
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000344 register(get_resources_on_service_clients)
Ken'ichi Ohmichib8461cb2015-11-20 08:10:51 +0000345 register(delete_resources_on_service_clients)
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700346 register(dont_import_local_tempest_into_lib)
Matthew Treinish59d9eaa2016-05-31 23:42:55 -0400347 register(dont_use_config_in_tempest_lib)
Ken'ichi Ohmichid079c892016-04-19 11:23:36 -0700348 register(use_rand_uuid_instead_of_uuid4)
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700349 register(dont_put_admin_tests_on_nonadmin_path)
junbolibc2ae862017-07-29 15:46:48 +0800350 register(unsupported_exception_attribute_PY3)
Felipe Monteiro4d011af2018-07-18 00:11:48 -0400351 register(negative_test_attribute_always_applied_to_negative_tests)