blob: a57a360880e58097ece792220c0c9e7be6a42d64 [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
Matthew Treinishaaa35952014-05-02 18:50:16 -040018import pep8
19
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+|\))')
Giampaolo Lauria1b837ce2013-05-01 11:22:07 -040037
38
ghanshyam50f19472014-11-26 17:04:37 +090039def import_no_clients_in_api_and_scenario_tests(physical_line, filename):
40 """Check for client imports from tempest/api & tempest/scenario tests
Giampaolo Lauria1b837ce2013-05-01 11:22:07 -040041
Giampaolo Lauriab8424eb2013-05-23 15:56:21 -040042 T102: Cannot import OpenStack python clients
43 """
Giampaolo Lauria1b837ce2013-05-01 11:22:07 -040044
ghanshyam50f19472014-11-26 17:04:37 +090045 if "tempest/api" in filename or "tempest/scenario" in filename:
Giampaolo Lauriab8424eb2013-05-23 15:56:21 -040046 res = PYTHON_CLIENT_RE.match(physical_line)
47 if res:
48 return (physical_line.find(res.group(1)),
49 ("T102: python clients import not allowed"
ghanshyam50f19472014-11-26 17:04:37 +090050 " in tempest/api/* or tempest/scenario/* tests"))
Giampaolo Lauriad50c27d2013-05-23 15:23:12 -040051
52
Matthew Treinish6ba951a2013-09-09 22:06:18 +000053def scenario_tests_need_service_tags(physical_line, filename,
54 previous_logical):
55 """Check that scenario tests have service tags
56
57 T104: Scenario tests require a services decorator
58 """
59
Matthew Treinishb12ad762014-06-19 10:18:05 -040060 if 'tempest/scenario/' in filename and '/test_' in filename:
Matthew Treinish6ba951a2013-09-09 22:06:18 +000061 if TEST_DEFINITION.match(physical_line):
62 if not SCENARIO_DECORATOR.match(previous_logical):
63 return (physical_line.find('def'),
64 "T104: Scenario tests require a service decorator")
65
66
Andrea Frittoli41fa16d2014-09-15 13:41:37 +010067def no_setup_teardown_class_for_tests(physical_line, filename):
Matthew Treinishaaa35952014-05-02 18:50:16 -040068
69 if pep8.noqa(physical_line):
70 return
71
Matthew Treinish9e26ca82016-02-23 11:43:20 -050072 if 'tempest/test.py' in filename or 'tempest/lib/' in filename:
73 return
74
75 if SETUP_TEARDOWN_CLASS_DEFINITION.match(physical_line):
76 return (physical_line.find('def'),
77 "T105: (setUp|tearDown)Class can not be used in tests")
Matthew Treinishecf212c2013-12-06 18:23:54 +000078
79
Masayuki Igawafcacf962014-02-19 14:00:01 +090080def no_vi_headers(physical_line, line_number, lines):
81 """Check for vi editor configuration in source files.
82
83 By default vi modelines can only appear in the first or
84 last 5 lines of a source file.
85
86 T106
87 """
88 # NOTE(gilliard): line_number is 1-indexed
89 if line_number <= 5 or line_number > len(lines) - 5:
90 if VI_HEADER_RE.match(physical_line):
91 return 0, "T106: Don't put vi configuration in source files"
92
93
Matthew Treinish662bc3c2014-04-07 17:55:39 -040094def service_tags_not_in_module_path(physical_line, filename):
95 """Check that a service tag isn't in the module path
96
97 A service tag should only be added if the service name isn't already in
98 the module path.
99
100 T107
101 """
102 # NOTE(mtreinish) Scenario tests always need service tags, but subdirs are
103 # created for services like heat which would cause false negatives for
104 # those tests, so just exclude the scenario tests.
105 if 'tempest/scenario' not in filename:
106 matches = SCENARIO_DECORATOR.match(physical_line)
107 if matches:
108 services = matches.group(1).split(',')
109 for service in services:
110 service_name = service.strip().strip("'")
111 modulepath = os.path.split(filename)[0]
112 if service_name in modulepath:
113 return (physical_line.find(service_name),
114 "T107: service tag should not be in path")
115
116
Ken'ichi Ohmichi80369a92015-04-06 23:41:14 +0000117def no_hyphen_at_end_of_rand_name(logical_line, filename):
118 """Check no hyphen at the end of rand_name() argument
119
120 T108
121 """
Ken'ichi Ohmichi80369a92015-04-06 23:41:14 +0000122 msg = "T108: hyphen should not be specified at the end of rand_name()"
123 if RAND_NAME_HYPHEN_RE.match(logical_line):
124 return 0, msg
125
126
Ghanshyam2a180b82014-06-16 13:54:22 +0900127def no_mutable_default_args(logical_line):
128 """Check that mutable object isn't used as default argument
129
130 N322: Method's default argument shouldn't be mutable
131 """
132 msg = "N322: Method's default argument shouldn't be mutable!"
133 if mutable_default_args.match(logical_line):
134 yield (0, msg)
135
136
John Warren3059a092015-08-31 15:34:49 -0400137def no_testtools_skip_decorator(logical_line):
138 """Check that methods do not have the testtools.skip decorator
139
140 T109
141 """
142 if TESTTOOLS_SKIP_DECORATOR.match(logical_line):
143 yield (0, "T109: Cannot use testtools.skip decorator; instead use "
Andrea Frittoli (andreaf)1370baf2016-04-29 14:26:22 -0500144 "decorators.skip_because from tempest.lib")
John Warren3059a092015-08-31 15:34:49 -0400145
146
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000147def _common_service_clients_check(logical_line, physical_line, filename,
148 ignored_list_file=None):
Ken'ichi Ohmichi12b28e92016-04-06 10:43:51 -0700149 if not re.match('tempest/(lib/)?services/.*', filename):
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000150 return False
151
152 if ignored_list_file is not None:
153 ignored_list = []
154 with open('tempest/hacking/' + ignored_list_file) as f:
155 for line in f:
156 ignored_list.append(line.strip())
157
158 if filename in ignored_list:
159 return False
160
161 if not METHOD.match(physical_line):
162 return False
163
164 if pep8.noqa(physical_line):
165 return False
166
167 return True
168
169
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000170def get_resources_on_service_clients(logical_line, physical_line, filename,
171 line_number, lines):
172 """Check that service client names of GET should be consistent
173
174 T110
175 """
Ken'ichi Ohmichi53346602015-11-20 07:23:54 +0000176 if not _common_service_clients_check(logical_line, physical_line,
177 filename, 'ignored_list_T110.txt'):
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000178 return
179
180 for line in lines[line_number:]:
181 if METHOD.match(line) or CLASS.match(line):
182 # the end of a method
183 return
184
Ken'ichi Ohmichif878e6e2016-01-13 05:10:17 +0000185 if 'self.get(' not in line and ('self.show_resource(' not in line and
186 'self.list_resources(' not in line):
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000187 continue
188
189 if METHOD_GET_RESOURCE.match(logical_line):
190 return
191
192 msg = ("T110: [GET /resources] methods should be list_<resource name>s"
193 " or show_<resource name>")
194 yield (0, msg)
195
196
Ken'ichi Ohmichib8461cb2015-11-20 08:10:51 +0000197def delete_resources_on_service_clients(logical_line, physical_line, filename,
198 line_number, lines):
199 """Check that service client names of DELETE should be consistent
200
201 T111
202 """
203 if not _common_service_clients_check(logical_line, physical_line,
204 filename, 'ignored_list_T111.txt'):
205 return
206
207 for line in lines[line_number:]:
208 if METHOD.match(line) or CLASS.match(line):
209 # the end of a method
210 return
211
Ken'ichi Ohmichif878e6e2016-01-13 05:10:17 +0000212 if 'self.delete(' not in line and 'self.delete_resource(' not in line:
Ken'ichi Ohmichib8461cb2015-11-20 08:10:51 +0000213 continue
214
215 if METHOD_DELETE_RESOURCE.match(logical_line):
216 return
217
218 msg = ("T111: [DELETE /resources/<id>] methods should be "
219 "delete_<resource name>")
220 yield (0, msg)
221
222
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700223def dont_import_local_tempest_into_lib(logical_line, filename):
224 """Check that tempest.lib should not import local tempest code
225
226 T112
227 """
228 if 'tempest/lib/' not in filename:
229 return
230
Federico Ressi2d6bcaa2018-04-11 12:37:36 +0200231 if not ('from tempest' in logical_line or
232 'import tempest' in logical_line):
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700233 return
234
Federico Ressi2d6bcaa2018-04-11 12:37:36 +0200235 if ('from tempest.lib' in logical_line or
236 'import tempest.lib' in logical_line):
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700237 return
238
239 msg = ("T112: tempest.lib should not import local tempest code to avoid "
240 "circular dependency")
241 yield (0, msg)
242
243
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
Matthew Treinish59d9eaa2016-05-31 23:42:55 -0400260def dont_use_config_in_tempest_lib(logical_line, filename):
261 """Check that tempest.lib doesn't use tempest config
262
263 T114
264 """
265
266 if 'tempest/lib/' not in filename:
267 return
268
Federico Ressi2d6bcaa2018-04-11 12:37:36 +0200269 if ('tempest.config' in logical_line or
270 'from tempest import config' in logical_line or
271 'oslo_config' in logical_line):
Matthew Treinish59d9eaa2016-05-31 23:42:55 -0400272 msg = ('T114: tempest.lib can not have any dependency on tempest '
273 'config.')
274 yield(0, msg)
275
276
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700277def dont_put_admin_tests_on_nonadmin_path(logical_line, physical_line,
278 filename):
279 """Check admin tests should exist under admin path
280
281 T115
282 """
283
284 if 'tempest/api/' not in filename:
285 return
286
287 if pep8.noqa(physical_line):
288 return
289
Stephen Finucane7f4a6212018-07-06 13:58:21 +0100290 if not re.match(r'class .*Test.*\(.*Admin.*\):', logical_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'.\/tempest\/api\/.*\/admin\/.*', filename):
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700294 msg = 'T115: All admin tests should exist under admin path.'
295 yield(0, msg)
296
297
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
Giampaolo Lauriad50c27d2013-05-23 15:23:12 -0400309def factory(register):
ghanshyam50f19472014-11-26 17:04:37 +0900310 register(import_no_clients_in_api_and_scenario_tests)
Matthew Treinish6ba951a2013-09-09 22:06:18 +0000311 register(scenario_tests_need_service_tags)
Andrea Frittoli41fa16d2014-09-15 13:41:37 +0100312 register(no_setup_teardown_class_for_tests)
Masayuki Igawafcacf962014-02-19 14:00:01 +0900313 register(no_vi_headers)
Matthew Treinish662bc3c2014-04-07 17:55:39 -0400314 register(service_tags_not_in_module_path)
Ken'ichi Ohmichi80369a92015-04-06 23:41:14 +0000315 register(no_hyphen_at_end_of_rand_name)
Ghanshyam2a180b82014-06-16 13:54:22 +0900316 register(no_mutable_default_args)
John Warren3059a092015-08-31 15:34:49 -0400317 register(no_testtools_skip_decorator)
Ken'ichi Ohmichic0d96be2015-11-11 12:33:48 +0000318 register(get_resources_on_service_clients)
Ken'ichi Ohmichib8461cb2015-11-20 08:10:51 +0000319 register(delete_resources_on_service_clients)
Ken'ichi Ohmichi0dc97472016-03-25 15:10:08 -0700320 register(dont_import_local_tempest_into_lib)
Matthew Treinish59d9eaa2016-05-31 23:42:55 -0400321 register(dont_use_config_in_tempest_lib)
Ken'ichi Ohmichid079c892016-04-19 11:23:36 -0700322 register(use_rand_uuid_instead_of_uuid4)
Ken'ichi Ohmichif741d0b2017-05-01 16:56:14 -0700323 register(dont_put_admin_tests_on_nonadmin_path)
junbolibc2ae862017-07-29 15:46:48 +0800324 register(unsupported_exception_attribute_PY3)