Hacking rule to check i18n usage
* Detect neutron.i18n import (neutron._i18n is recommended) * Check builtins _ usage * 'builtins = _' in tox.ini is no longer required. * Introduce hacking rule doctest framework. Newly added check_builtins_gettext() hacking check takes token as argument. It is not a good idea to pass a tokenized line manually. Instead it is reasonable to use docstring based tests used in hacking repo. Change-Id: Ib7464658fc4c8a6f1b03af6ab46f0bd3ee0bfb18
This commit is contained in:
parent
1edb841eba
commit
44be13a2a6
|
@ -25,6 +25,8 @@ Neutron Specific Commandments
|
||||||
assertEqual(observed_http_code, expected_http_code).
|
assertEqual(observed_http_code, expected_http_code).
|
||||||
- [N333] Validate that LOG.warning is used instead of LOG.warn. The latter
|
- [N333] Validate that LOG.warning is used instead of LOG.warn. The latter
|
||||||
is deprecated.
|
is deprecated.
|
||||||
|
- [N340] Check usage of <module>.i18n (and neutron.i18n)
|
||||||
|
- [N341] Check usage of _ from python builtins
|
||||||
|
|
||||||
Creating Unit Tests
|
Creating Unit Tests
|
||||||
-------------------
|
-------------------
|
||||||
|
|
|
@ -25,6 +25,7 @@ else:
|
||||||
gettext.install('neutron')
|
gettext.install('neutron')
|
||||||
|
|
||||||
|
|
||||||
|
# flake8: noqa
|
||||||
six.moves.builtins.__dict__['_'] = removals.remove(
|
six.moves.builtins.__dict__['_'] = removals.remove(
|
||||||
message='Builtin _ translation function is deprecated in OpenStack; '
|
message='Builtin _ translation function is deprecated in OpenStack; '
|
||||||
'use the function from _i18n module for your project.')(_)
|
'use the function from _i18n module for your project.')(_)
|
||||||
|
|
|
@ -71,7 +71,7 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase):
|
||||||
if self.rtr_fip_subnet is None:
|
if self.rtr_fip_subnet is None:
|
||||||
self.rtr_fip_subnet = self.fip_ns.local_subnets.allocate(
|
self.rtr_fip_subnet = self.fip_ns.local_subnets.allocate(
|
||||||
self.router_id)
|
self.router_id)
|
||||||
rtr_2_fip, _ = self.rtr_fip_subnet.get_pair()
|
rtr_2_fip, __ = self.rtr_fip_subnet.get_pair()
|
||||||
device = ip_lib.IPDevice(fip_2_rtr_name, namespace=fip_ns_name)
|
device = ip_lib.IPDevice(fip_2_rtr_name, namespace=fip_ns_name)
|
||||||
device.route.add_route(fip_cidr, str(rtr_2_fip.ip))
|
device.route.add_route(fip_cidr, str(rtr_2_fip.ip))
|
||||||
interface_name = (
|
interface_name = (
|
||||||
|
|
|
@ -25,6 +25,7 @@ from sqlalchemy.orm import exc as sa_exc
|
||||||
|
|
||||||
from neutron_lib import constants as lib_consts
|
from neutron_lib import constants as lib_consts
|
||||||
|
|
||||||
|
from neutron._i18n import _
|
||||||
from neutron.api.v2 import attributes as attr
|
from neutron.api.v2 import attributes as attr
|
||||||
from neutron.common import exceptions as n_exc
|
from neutron.common import exceptions as n_exc
|
||||||
from neutron.db import address_scope_db
|
from neutron.db import address_scope_db
|
||||||
|
|
|
@ -19,6 +19,7 @@ from sqlalchemy.orm import exc
|
||||||
from sqlalchemy import sql
|
from sqlalchemy import sql
|
||||||
from sqlalchemy.sql import expression as expr
|
from sqlalchemy.sql import expression as expr
|
||||||
|
|
||||||
|
from neutron._i18n import _
|
||||||
from neutron.api.v2 import attributes
|
from neutron.api.v2 import attributes
|
||||||
from neutron.callbacks import events
|
from neutron.callbacks import events
|
||||||
from neutron.callbacks import exceptions as c_exc
|
from neutron.callbacks import exceptions as c_exc
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
import netaddr
|
import netaddr
|
||||||
from sqlalchemy import types
|
from sqlalchemy import types
|
||||||
|
|
||||||
|
from neutron._i18n import _
|
||||||
|
|
||||||
|
|
||||||
class IPAddress(types.TypeDecorator):
|
class IPAddress(types.TypeDecorator):
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,23 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import pep8
|
import pep8
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
def flake8ext(f):
|
||||||
|
"""Decorator to indicate flake8 extension.
|
||||||
|
|
||||||
|
This is borrowed from hacking.core.flake8ext(), but at now it is used
|
||||||
|
only for unit tests to know which are neutron flake8 extensions.
|
||||||
|
"""
|
||||||
|
f.name = __name__
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
# Guidelines for writing new hacking checks
|
# Guidelines for writing new hacking checks
|
||||||
#
|
#
|
||||||
# - Use only for Neutron specific tests. OpenStack general tests
|
# - Use only for Neutron specific tests. OpenStack general tests
|
||||||
|
@ -59,6 +71,7 @@ log_warn = re.compile(
|
||||||
contextlib_nested = re.compile(r"^with (contextlib\.)?nested\(")
|
contextlib_nested = re.compile(r"^with (contextlib\.)?nested\(")
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def validate_log_translations(logical_line, physical_line, filename):
|
def validate_log_translations(logical_line, physical_line, filename):
|
||||||
# Translations are not required in the test directory
|
# Translations are not required in the test directory
|
||||||
if "neutron/tests" in filename:
|
if "neutron/tests" in filename:
|
||||||
|
@ -71,6 +84,7 @@ def validate_log_translations(logical_line, physical_line, filename):
|
||||||
yield (0, msg)
|
yield (0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def use_jsonutils(logical_line, filename):
|
def use_jsonutils(logical_line, filename):
|
||||||
msg = "N321: jsonutils.%(fun)s must be used instead of json.%(fun)s"
|
msg = "N321: jsonutils.%(fun)s must be used instead of json.%(fun)s"
|
||||||
|
|
||||||
|
@ -93,6 +107,7 @@ def use_jsonutils(logical_line, filename):
|
||||||
yield (pos, msg % {'fun': f[:-1]})
|
yield (pos, msg % {'fun': f[:-1]})
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def no_translate_debug_logs(logical_line, filename):
|
def no_translate_debug_logs(logical_line, filename):
|
||||||
"""Check for 'LOG.debug(_(' and 'LOG.debug(_Lx('
|
"""Check for 'LOG.debug(_(' and 'LOG.debug(_Lx('
|
||||||
|
|
||||||
|
@ -108,6 +123,7 @@ def no_translate_debug_logs(logical_line, filename):
|
||||||
yield(0, "N319 Don't translate debug level logs")
|
yield(0, "N319 Don't translate debug level logs")
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def check_assert_called_once_with(logical_line, filename):
|
def check_assert_called_once_with(logical_line, filename):
|
||||||
# Try to detect unintended calls of nonexistent mock methods like:
|
# Try to detect unintended calls of nonexistent mock methods like:
|
||||||
# assert_called_once
|
# assert_called_once
|
||||||
|
@ -131,6 +147,7 @@ def check_assert_called_once_with(logical_line, filename):
|
||||||
yield (0, msg)
|
yield (0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def check_no_contextlib_nested(logical_line, filename):
|
def check_no_contextlib_nested(logical_line, filename):
|
||||||
msg = ("N324: contextlib.nested is deprecated. With Python 2.7 and later "
|
msg = ("N324: contextlib.nested is deprecated. With Python 2.7 and later "
|
||||||
"the with-statement supports multiple nested objects. See https://"
|
"the with-statement supports multiple nested objects. See https://"
|
||||||
|
@ -141,12 +158,14 @@ def check_no_contextlib_nested(logical_line, filename):
|
||||||
yield(0, msg)
|
yield(0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def check_python3_xrange(logical_line):
|
def check_python3_xrange(logical_line):
|
||||||
if re.search(r"\bxrange\s*\(", logical_line):
|
if re.search(r"\bxrange\s*\(", logical_line):
|
||||||
yield(0, "N325: Do not use xrange. Use range, or six.moves.range for "
|
yield(0, "N325: Do not use xrange. Use range, or six.moves.range for "
|
||||||
"large loops.")
|
"large loops.")
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def check_no_basestring(logical_line):
|
def check_no_basestring(logical_line):
|
||||||
if re.search(r"\bbasestring\b", logical_line):
|
if re.search(r"\bbasestring\b", logical_line):
|
||||||
msg = ("N326: basestring is not Python3-compatible, use "
|
msg = ("N326: basestring is not Python3-compatible, use "
|
||||||
|
@ -154,12 +173,14 @@ def check_no_basestring(logical_line):
|
||||||
yield(0, msg)
|
yield(0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def check_python3_no_iteritems(logical_line):
|
def check_python3_no_iteritems(logical_line):
|
||||||
if re.search(r".*\.iteritems\(\)", logical_line):
|
if re.search(r".*\.iteritems\(\)", logical_line):
|
||||||
msg = ("N327: Use six.iteritems() instead of dict.iteritems().")
|
msg = ("N327: Use six.iteritems() instead of dict.iteritems().")
|
||||||
yield(0, msg)
|
yield(0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def check_asserttrue(logical_line, filename):
|
def check_asserttrue(logical_line, filename):
|
||||||
if 'neutron/tests/' in filename:
|
if 'neutron/tests/' in filename:
|
||||||
if re.search(r"assertEqual\(\s*True,[^,]*(,[^,]*)?\)", logical_line):
|
if re.search(r"assertEqual\(\s*True,[^,]*(,[^,]*)?\)", logical_line):
|
||||||
|
@ -172,12 +193,14 @@ def check_asserttrue(logical_line, filename):
|
||||||
yield (0, msg)
|
yield (0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def no_mutable_default_args(logical_line):
|
def no_mutable_default_args(logical_line):
|
||||||
msg = "N329: Method's default argument shouldn't be mutable!"
|
msg = "N329: Method's default argument shouldn't be mutable!"
|
||||||
if mutable_default_args.match(logical_line):
|
if mutable_default_args.match(logical_line):
|
||||||
yield (0, msg)
|
yield (0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def check_assertfalse(logical_line, filename):
|
def check_assertfalse(logical_line, filename):
|
||||||
if 'neutron/tests/' in filename:
|
if 'neutron/tests/' in filename:
|
||||||
if re.search(r"assertEqual\(\s*False,[^,]*(,[^,]*)?\)", logical_line):
|
if re.search(r"assertEqual\(\s*False,[^,]*(,[^,]*)?\)", logical_line):
|
||||||
|
@ -190,6 +213,7 @@ def check_assertfalse(logical_line, filename):
|
||||||
yield (0, msg)
|
yield (0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def check_assertempty(logical_line, filename):
|
def check_assertempty(logical_line, filename):
|
||||||
if 'neutron/tests/' in filename:
|
if 'neutron/tests/' in filename:
|
||||||
msg = ("N330: Use assertEqual(*empty*, observed) instead of "
|
msg = ("N330: Use assertEqual(*empty*, observed) instead of "
|
||||||
|
@ -201,6 +225,7 @@ def check_assertempty(logical_line, filename):
|
||||||
yield (0, msg)
|
yield (0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def check_assertisinstance(logical_line, filename):
|
def check_assertisinstance(logical_line, filename):
|
||||||
if 'neutron/tests/' in filename:
|
if 'neutron/tests/' in filename:
|
||||||
if re.search(r"assertTrue\(\s*isinstance\(\s*[^,]*,\s*[^,]*\)\)",
|
if re.search(r"assertTrue\(\s*isinstance\(\s*[^,]*,\s*[^,]*\)\)",
|
||||||
|
@ -210,6 +235,7 @@ def check_assertisinstance(logical_line, filename):
|
||||||
yield (0, msg)
|
yield (0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def check_assertequal_for_httpcode(logical_line, filename):
|
def check_assertequal_for_httpcode(logical_line, filename):
|
||||||
msg = ("N332: Use assertEqual(expected_http_code, observed_http_code) "
|
msg = ("N332: Use assertEqual(expected_http_code, observed_http_code) "
|
||||||
"instead of assertEqual(observed_http_code, expected_http_code)")
|
"instead of assertEqual(observed_http_code, expected_http_code)")
|
||||||
|
@ -219,12 +245,82 @@ def check_assertequal_for_httpcode(logical_line, filename):
|
||||||
yield (0, msg)
|
yield (0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
def check_log_warn_deprecated(logical_line, filename):
|
def check_log_warn_deprecated(logical_line, filename):
|
||||||
msg = "N333: Use LOG.warning due to compatibility with py3"
|
msg = "N333: Use LOG.warning due to compatibility with py3"
|
||||||
if log_warn.match(logical_line):
|
if log_warn.match(logical_line):
|
||||||
yield (0, msg)
|
yield (0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
|
def check_oslo_i18n_wrapper(logical_line, filename, noqa):
|
||||||
|
"""Check for neutron.i18n usage.
|
||||||
|
|
||||||
|
Okay(neutron/foo/bar.py): from neutron._i18n import _
|
||||||
|
Okay(neutron_lbaas/foo/bar.py): from neutron_lbaas._i18n import _
|
||||||
|
N340(neutron/foo/bar.py): from neutron.i18n import _
|
||||||
|
N340(neutron_lbaas/foo/bar.py): from neutron_lbaas.i18n import _
|
||||||
|
N340(neutron_lbaas/foo/bar.py): from neutron.i18n import _
|
||||||
|
N340(neutron_lbaas/foo/bar.py): from neutron._i18n import _
|
||||||
|
Okay(neutron/foo/bar.py): from neutron.i18n import _ # noqa
|
||||||
|
"""
|
||||||
|
|
||||||
|
if noqa:
|
||||||
|
return
|
||||||
|
|
||||||
|
split_line = logical_line.split()
|
||||||
|
modulename = os.path.normpath(filename).split('/')[0]
|
||||||
|
bad_i18n_module = '%s.i18n' % modulename
|
||||||
|
|
||||||
|
if (len(split_line) > 1 and split_line[0] in ('import', 'from')):
|
||||||
|
if (split_line[1] == bad_i18n_module or
|
||||||
|
modulename != 'neutron' and split_line[1] in ('neutron.i18n',
|
||||||
|
'neutron._i18n')):
|
||||||
|
msg = ("N340: %(found)s is found. Use %(module)s._i18n instead."
|
||||||
|
% {'found': split_line[1], 'module': modulename})
|
||||||
|
yield (0, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@flake8ext
|
||||||
|
def check_builtins_gettext(logical_line, tokens, filename, lines, noqa):
|
||||||
|
"""Check usage of builtins gettext _().
|
||||||
|
|
||||||
|
Okay(neutron/foo.py): from neutron._i18n import _\n_('foo')
|
||||||
|
N341(neutron/foo.py): _('foo')
|
||||||
|
Okay(neutron/_i18n.py): _('foo')
|
||||||
|
Okay(neutron/i18n.py): _('foo')
|
||||||
|
Okay(neutron/foo.py): _('foo') # noqa
|
||||||
|
"""
|
||||||
|
|
||||||
|
if noqa:
|
||||||
|
return
|
||||||
|
|
||||||
|
modulename = os.path.normpath(filename).split('/')[0]
|
||||||
|
|
||||||
|
if '%s/tests' % modulename in filename:
|
||||||
|
return
|
||||||
|
|
||||||
|
if os.path.basename(filename) in ('i18n.py', '_i18n.py'):
|
||||||
|
return
|
||||||
|
|
||||||
|
token_values = [t[1] for t in tokens]
|
||||||
|
i18n_wrapper = '%s._i18n' % modulename
|
||||||
|
|
||||||
|
if '_' in token_values:
|
||||||
|
i18n_import_line_found = False
|
||||||
|
for line in lines:
|
||||||
|
split_line = [elm.rstrip(',') for elm in line.split()]
|
||||||
|
if (len(split_line) > 1 and split_line[0] == 'from' and
|
||||||
|
split_line[1] == i18n_wrapper and
|
||||||
|
'_' in split_line):
|
||||||
|
i18n_import_line_found = True
|
||||||
|
break
|
||||||
|
if not i18n_import_line_found:
|
||||||
|
msg = ("N341: _ from python builtins module is used. "
|
||||||
|
"Use _ from %s instead." % i18n_wrapper)
|
||||||
|
yield (0, msg)
|
||||||
|
|
||||||
|
|
||||||
def factory(register):
|
def factory(register):
|
||||||
register(validate_log_translations)
|
register(validate_log_translations)
|
||||||
register(use_jsonutils)
|
register(use_jsonutils)
|
||||||
|
@ -241,3 +337,5 @@ def factory(register):
|
||||||
register(check_assertisinstance)
|
register(check_assertisinstance)
|
||||||
register(check_assertequal_for_httpcode)
|
register(check_assertequal_for_httpcode)
|
||||||
register(check_log_warn_deprecated)
|
register(check_log_warn_deprecated)
|
||||||
|
register(check_oslo_i18n_wrapper)
|
||||||
|
register(check_builtins_gettext)
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import pecan
|
import pecan
|
||||||
|
|
||||||
|
from neutron._i18n import _
|
||||||
from neutron.api import extensions
|
from neutron.api import extensions
|
||||||
from neutron.pecan_wsgi.controllers import utils
|
from neutron.pecan_wsgi.controllers import utils
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,8 @@ from oslo_log import log as logging
|
||||||
import pecan
|
import pecan
|
||||||
from pecan import request
|
from pecan import request
|
||||||
|
|
||||||
|
from neutron._i18n import _LW
|
||||||
from neutron.api import api_common
|
from neutron.api import api_common
|
||||||
from neutron.i18n import _LW
|
|
||||||
from neutron import manager
|
from neutron import manager
|
||||||
from neutron.pecan_wsgi.controllers import utils
|
from neutron.pecan_wsgi.controllers import utils
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from neutron._i18n import _
|
||||||
from neutron.agent.common import config
|
from neutron.agent.common import config
|
||||||
|
|
||||||
agent_opts = [
|
agent_opts = [
|
||||||
|
|
|
@ -185,7 +185,7 @@ class L2populationMechanismDriver(api.MechanismDriver):
|
||||||
|
|
||||||
def _get_tunnels(self, tunnel_network_ports, exclude_host):
|
def _get_tunnels(self, tunnel_network_ports, exclude_host):
|
||||||
agents = {}
|
agents = {}
|
||||||
for _, agent in tunnel_network_ports:
|
for __, agent in tunnel_network_ports:
|
||||||
if agent.host == exclude_host:
|
if agent.host == exclude_host:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from neutron._i18n import _
|
||||||
|
|
||||||
DEFAULT_INTERFACE_MAPPINGS = []
|
DEFAULT_INTERFACE_MAPPINGS = []
|
||||||
|
|
||||||
macvtap_opts = [
|
macvtap_opts = [
|
||||||
|
|
|
@ -10,7 +10,15 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from flake8 import engine
|
||||||
|
from hacking.tests import test_doctest as hacking_doctest
|
||||||
|
import pep8
|
||||||
|
import testscenarios
|
||||||
import testtools
|
import testtools
|
||||||
|
from testtools import content
|
||||||
|
from testtools import matchers
|
||||||
|
|
||||||
from neutron.hacking import checks
|
from neutron.hacking import checks
|
||||||
from neutron.tests import base
|
from neutron.tests import base
|
||||||
|
@ -273,3 +281,84 @@ class HackingTestCase(base.BaseTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
0, len(list(checks.check_assertequal_for_httpcode(pass_code,
|
0, len(list(checks.check_assertequal_for_httpcode(pass_code,
|
||||||
"neutron/tests/test_assert.py"))))
|
"neutron/tests/test_assert.py"))))
|
||||||
|
|
||||||
|
# The following is borrowed from hacking/tests/test_doctest.py.
|
||||||
|
# Tests defined in docstring is easier to understand
|
||||||
|
# in some cases, for example, hacking rules which take tokens as argument.
|
||||||
|
|
||||||
|
# TODO(amotoki): Migrate existing unit tests above to docstring tests.
|
||||||
|
# NOTE(amotoki): Is it better to enhance HackingDocTestCase in hacking repo to
|
||||||
|
# pass filename to pep8.Checker so that we can reuse it in this test.
|
||||||
|
# I am not sure whether unit test class is public.
|
||||||
|
|
||||||
|
SELFTEST_REGEX = re.compile(r'\b(Okay|N\d{3})(\((\S+)\))?:\s(.*)')
|
||||||
|
|
||||||
|
|
||||||
|
# Each scenario is (name, dict(filename=..., lines=.., options=..., code=...))
|
||||||
|
file_cases = []
|
||||||
|
|
||||||
|
|
||||||
|
class HackingDocTestCase(hacking_doctest.HackingTestCase):
|
||||||
|
|
||||||
|
scenarios = file_cases
|
||||||
|
|
||||||
|
def test_pep8(self):
|
||||||
|
|
||||||
|
# NOTE(jecarey): Add tests marked as off_by_default to enable testing
|
||||||
|
turn_on = set(['H106'])
|
||||||
|
if self.options.select:
|
||||||
|
turn_on.update(self.options.select)
|
||||||
|
self.options.select = tuple(turn_on)
|
||||||
|
|
||||||
|
report = pep8.BaseReport(self.options)
|
||||||
|
checker = pep8.Checker(filename=self.filename, lines=self.lines,
|
||||||
|
options=self.options, report=report)
|
||||||
|
checker.check_all()
|
||||||
|
self.addDetail('doctest', content.text_content(self.raw))
|
||||||
|
if self.code == 'Okay':
|
||||||
|
self.assertThat(
|
||||||
|
len(report.counters),
|
||||||
|
matchers.Not(matchers.GreaterThan(
|
||||||
|
len(self.options.benchmark_keys))),
|
||||||
|
"incorrectly found %s" % ', '.join(
|
||||||
|
[key for key in report.counters
|
||||||
|
if key not in self.options.benchmark_keys]))
|
||||||
|
else:
|
||||||
|
self.addDetail('reason',
|
||||||
|
content.text_content("Failed to trigger rule %s" %
|
||||||
|
self.code))
|
||||||
|
self.assertIn(self.code, report.counters)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_lines(check):
|
||||||
|
for line in check.__doc__.splitlines():
|
||||||
|
line = line.lstrip()
|
||||||
|
match = SELFTEST_REGEX.match(line)
|
||||||
|
if match is None:
|
||||||
|
continue
|
||||||
|
yield (line, match.groups())
|
||||||
|
|
||||||
|
|
||||||
|
def load_tests(loader, tests, pattern):
|
||||||
|
|
||||||
|
flake8_style = engine.get_style_guide(parse_argv=False,
|
||||||
|
# Ignore H104 otherwise it's
|
||||||
|
# raised on doctests.
|
||||||
|
ignore=('F', 'H104'))
|
||||||
|
options = flake8_style.options
|
||||||
|
|
||||||
|
for name, check in checks.__dict__.items():
|
||||||
|
if not hasattr(check, 'name'):
|
||||||
|
continue
|
||||||
|
if check.name != checks.__name__:
|
||||||
|
continue
|
||||||
|
if not check.__doc__:
|
||||||
|
continue
|
||||||
|
for (lineno, (raw, line)) in enumerate(_get_lines(check)):
|
||||||
|
code, __, filename, source = line
|
||||||
|
lines = [part.replace(r'\t', '\t') + '\n'
|
||||||
|
for part in source.split(r'\n')]
|
||||||
|
file_cases.append(("%s-line-%s" % (name, lineno),
|
||||||
|
dict(lines=lines, raw=raw, options=options,
|
||||||
|
code=code, filename=filename)))
|
||||||
|
return testscenarios.load_tests_apply_scenarios(loader, tests, pattern)
|
||||||
|
|
3
tox.ini
3
tox.ini
|
@ -133,14 +133,13 @@ commands = sphinx-build -W -b html doc/source doc/build/html
|
||||||
# H405 multi line docstring summary not separated with an empty line
|
# H405 multi line docstring summary not separated with an empty line
|
||||||
ignore = E125,E126,E128,E129,E265,H404,H405
|
ignore = E125,E126,E128,E129,E265,H404,H405
|
||||||
show-source = true
|
show-source = true
|
||||||
builtins = _
|
|
||||||
# neutron/tests/tempest needs to be excluded so long as it continues
|
# neutron/tests/tempest needs to be excluded so long as it continues
|
||||||
# to be copied directly from tempest, since tempest and neutron do not
|
# to be copied directly from tempest, since tempest and neutron do not
|
||||||
# share a flake8 configuration.
|
# share a flake8 configuration.
|
||||||
exclude = ./.*,build,dist,neutron/openstack/common/*,neutron/tests/tempest
|
exclude = ./.*,build,dist,neutron/openstack/common/*,neutron/tests/tempest
|
||||||
|
|
||||||
[hacking]
|
[hacking]
|
||||||
import_exceptions = neutron.i18n, neutron._i18n
|
import_exceptions = neutron._i18n
|
||||||
local-check-factory = neutron.hacking.checks.factory
|
local-check-factory = neutron.hacking.checks.factory
|
||||||
|
|
||||||
[testenv:genconfig]
|
[testenv:genconfig]
|
||||||
|
|
Loading…
Reference in New Issue