... to enforce maximum line length, to keep consistent code format. Note that E501 check is disabled in test code now, until we decide how to update ~50 lines violating the limit due to too long names. Change-Id: I122c8b9035d6381dafb34438517c26b01e5201f5 Signed-off-by: Takashi Kajinami <kajinamit@oss.nttdata.com>
180 lines
5.6 KiB
Python
180 lines
5.6 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import ast
|
|
import os
|
|
import re
|
|
|
|
from hacking import core
|
|
|
|
"""
|
|
Guidelines for writing new hacking checks
|
|
|
|
- Use only for python-openstackclient specific tests. OpenStack general tests
|
|
should be submitted to the common 'hacking' module.
|
|
- Pick numbers in the range O4xx. Find the current test with the highest
|
|
allocated number and then pick the next value.
|
|
"""
|
|
|
|
|
|
@core.flake8ext
|
|
def assert_no_oslo(logical_line):
|
|
"""Check for use of oslo libraries.
|
|
|
|
O400
|
|
"""
|
|
if re.match(r'(from|import) oslo_.*', logical_line):
|
|
yield (0, "0400: oslo libraries should not be used in SDK projects")
|
|
|
|
|
|
@core.flake8ext
|
|
def assert_no_duplicated_setup(logical_line, filename):
|
|
"""Check for use of various unnecessary test duplications.
|
|
|
|
O401
|
|
"""
|
|
if os.path.join('openstackclient', 'tests', 'unit') not in filename:
|
|
return
|
|
|
|
if re.match(r'self.app = .*\(self.app, self.namespace\)', logical_line):
|
|
yield (
|
|
0,
|
|
'O401: It is not necessary to create dummy Namespace objects',
|
|
)
|
|
|
|
if os.path.basename(filename) != 'fakes.py':
|
|
if re.match(
|
|
r'self.[a-z_]+_client = self.app.client_manager.*', logical_line
|
|
):
|
|
yield (
|
|
0,
|
|
"O401: Aliases for mocks of the service client are already "
|
|
"provided by the respective service's FakeClientMixin class",
|
|
)
|
|
|
|
if match := re.match(
|
|
r'self.app.client_manager.([a-z_]+) = mock.Mock', logical_line
|
|
):
|
|
service = match.group(1)
|
|
if service == 'auth_ref':
|
|
return
|
|
yield (
|
|
0,
|
|
f"O401: client_manager.{service} mocks are already provided "
|
|
f"by the {service} service's FakeClientMixin class",
|
|
)
|
|
|
|
|
|
@core.flake8ext
|
|
def assert_use_of_client_aliases(logical_line):
|
|
"""Ensure we use $service_client instead of $sdk_connection.service.
|
|
|
|
O402
|
|
"""
|
|
# we should expand the list of services as we drop legacy clients
|
|
if match := re.match(
|
|
r'self\.app\.client_manager\.sdk_connnection\.(compute|network|image)',
|
|
logical_line,
|
|
):
|
|
service = match.group(1)
|
|
yield (0, f"0402: prefer {service}_client to sdk_connection.{service}")
|
|
|
|
if match := re.match(
|
|
r'(self\.app\.client_manager\.(compute|network|image)+\.[a-z_]+) = mock.Mock', # noqa: E501
|
|
logical_line,
|
|
):
|
|
yield (
|
|
0,
|
|
f"O402: {match.group(1)} is already a mock: there's no need to "
|
|
f"assign a new mock.Mock instance.",
|
|
)
|
|
|
|
if match := re.match(
|
|
r'(self\.(compute|network|image)_client\.[a-z_]+) = mock.Mock',
|
|
logical_line,
|
|
):
|
|
yield (
|
|
0,
|
|
f"O402: {match.group(1)} is already a mock: there's no need to "
|
|
f"assign a new mock.Mock instance.",
|
|
)
|
|
|
|
|
|
class SDKProxyFindChecker(ast.NodeVisitor):
|
|
"""NodeVisitor to find ``*_client.find_*`` statements."""
|
|
|
|
def __init__(self):
|
|
self.error = False
|
|
|
|
def visit_Call(self, node):
|
|
# No need to keep visiting the AST if we already found something.
|
|
if self.error:
|
|
return
|
|
|
|
self.generic_visit(node)
|
|
|
|
if not (
|
|
isinstance(node.func, ast.Attribute)
|
|
and node.func.attr.startswith('find_') # and
|
|
# isinstance(node.func.value, ast.Attribute) and
|
|
# node.func.value.attr.endswith('_client')
|
|
):
|
|
# print(f'skipping: got {node.func}')
|
|
return
|
|
|
|
if not (
|
|
(
|
|
# handle calls like 'identity_client.find_project'
|
|
isinstance(node.func.value, ast.Name)
|
|
and node.func.value.id.endswith('client')
|
|
)
|
|
or (
|
|
# handle calls like 'self.app.client_manager.image.find_image'
|
|
isinstance(node.func.value, ast.Attribute)
|
|
and node.func.value.attr
|
|
in ('identity', 'network', 'image', 'compute')
|
|
)
|
|
):
|
|
return
|
|
|
|
if not any(kw.arg == 'ignore_missing' for kw in node.keywords):
|
|
self.error = True
|
|
|
|
|
|
@core.flake8ext
|
|
def assert_find_ignore_missing_kwargs(logical_line, filename):
|
|
"""Ensure ignore_missing is always used for ``find_*`` SDK proxy calls.
|
|
|
|
Okay: self.compute_client.find_server(foo, ignore_missing=True)
|
|
Okay: self.image_client.find_server(foo, ignore_missing=False)
|
|
Okay: self.volume_client.volumes.find(name='foo')
|
|
O403: self.network_client.find_network(parsed_args.network)
|
|
O403: self.compute_client.find_flavor(flavor_id, get_extra_specs=True)
|
|
"""
|
|
if 'tests' in filename:
|
|
return
|
|
|
|
checker = SDKProxyFindChecker()
|
|
try:
|
|
parsed_logical_line = ast.parse(logical_line)
|
|
except SyntaxError:
|
|
# let flake8 catch this itself
|
|
# https://github.com/PyCQA/flake8/issues/1948
|
|
return
|
|
checker.visit(parsed_logical_line)
|
|
if checker.error:
|
|
yield (
|
|
0,
|
|
'O403: Calls to find_* proxy methods must explicitly set '
|
|
'ignore_missing',
|
|
)
|