Add hacking check to ensure _ is imported

Added hacking check to ensure that _ is imported and allows multiple
imports from i18n module per line.

Change-Id: Ieffabd6f2fe866000c5dc1d9ce83acd2f9ab0450
This commit is contained in:
poojajadhav 2017-02-06 16:09:42 +05:30
parent 7cfa3917a4
commit f8c25dd2aa
5 changed files with 164 additions and 2 deletions

View File

@ -2,3 +2,8 @@ masakari-monitors Style Commandments
===============================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
masakari-monitors Specific Commandments
------------------------------
- [M301] Ensure that the _() function is explicitly imported to ensure proper translations.

View File

View File

@ -0,0 +1,70 @@
# Copyright (c) 2017, NTT Data
# All Rights Reserved.
#
# 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 re
"""
Guidelines for writing new hacking checks
- Use only for masakarimonitors specific tests. OpenStack general tests
should be submitted to the common 'hacking' module.
- Pick numbers in the range M3xx. Find the current test with
the highest allocated number and then pick the next value.
- Keep the test method code in the source file ordered based
on the M3xx value.
- List the new rule in the top level HACKING.rst file
- Add test cases for each new rule to masakarimonitors/tests/unit/
test_hacking.py
"""
UNDERSCORE_IMPORT_FILES = []
translated_log = re.compile(
r"(.)*LOG\.(audit|error|info|critical|exception)"
"\(\s*_\(\s*('|\")")
string_translation = re.compile(r"[^_]*_\(\s*('|\")")
underscore_import_check = re.compile(r"(.)*import _(.)*")
underscore_import_check_multi = re.compile(r"(.)*i18n\s+import(.)* _, (.)*")
# We need this for cases where they have created their own _ function.
custom_underscore_check = re.compile(r"(.)*_\s*=\s*(.)*")
def check_explicit_underscore_import(logical_line, filename):
"""Check for explicit import of the _ function
We need to ensure that any files that are using the _() function
to translate logs are explicitly importing the _ function. We
can't trust unit test to catch whether the import has been
added so we need to check for it here.
"""
# Build a list of the files that have _ imported. No further
# checking needed once it is found.
for file in UNDERSCORE_IMPORT_FILES:
if file in filename:
return
if (underscore_import_check.match(logical_line) or
underscore_import_check_multi.match(logical_line) or
custom_underscore_check.match(logical_line)):
UNDERSCORE_IMPORT_FILES.append(filename)
elif(translated_log.match(logical_line) or
string_translation.match(logical_line)):
yield(0, "M301: Found use of _() without explicit import of _ !")
def factory(register):
register(check_explicit_underscore_import)

View File

@ -0,0 +1,79 @@
# Copyright 2017 NTT Data.
#
# 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 testtools
from masakarimonitors.hacking import checks
class HackingTestCase(testtools.TestCase):
"""This class tests the hacking checks in masakarimonitors.hacking.checks by
passing strings to the check methods like the pep8/flake8 parser would.
The parser loops over each line in the file and then passes the
parameters to the check method. The parameter names in the check method
dictate what type of object is passed to the check method.
The parameter types are::
logical_line: A processed line with the following modifications:
- Multi-line statements converted to a single line.
- Stripped left and right.
- Contents of strings replaced with "xxx" of same length.
- Comments removed.
physical_line: Raw line of text from the input file.
lines: a list of the raw lines from the input file
tokens: the tokens that contribute to this logical line
line_number: line number in the input file
total_lines: number of lines in the input file
blank_lines: blank lines before this one
indent_char: indentation character in this file (" " or "\t")
indent_level: indentation (with tabs expanded to multiples of 8)
previous_indent_level: indentation on previous line
previous_logical: previous logical line
filename: Path of the file being run through pep8
When running a test on a check method the return will be False/None if
there is no violation in the sample input. If there is an error a tuple is
returned with a position in the line, and a message. So to check the result
just assertTrue if the check is expected to fail and assertFalse if it
should pass.
"""
def test_check_explicit_underscore_import(self):
self.assertEqual(len(list(checks.check_explicit_underscore_import(
"LOG.info(_('My info message'))",
"masakarimonitors/tests/other_files.py"))), 1)
self.assertEqual(len(list(checks.check_explicit_underscore_import(
"msg = _('My message')",
"masakarimonitors/tests/other_files.py"))), 1)
self.assertEqual(len(list(checks.check_explicit_underscore_import(
"from masakarimonitors.i18n import _",
"masakarimonitors/tests/other_files.py"))), 0)
self.assertEqual(len(list(checks.check_explicit_underscore_import(
"LOG.info(_('My info message'))",
"masakarimonitors/tests/other_files.py"))), 0)
self.assertEqual(len(list(checks.check_explicit_underscore_import(
"msg = _('My message')",
"masakarimonitors/tests/other_files.py"))), 0)
self.assertEqual(len(list(checks.check_explicit_underscore_import(
"from masakarimonitors.i18n import _, _LW",
"masakarimonitors/tests/other_files2.py"))), 0)
self.assertEqual(len(list(checks.check_explicit_underscore_import(
"msg = _('My message')",
"masakarimonitors/tests/other_files2.py"))), 0)
self.assertEqual(len(list(checks.check_explicit_underscore_import(
"_ = translations.ugettext",
"masakarimonitors/tests/other_files3.py"))), 0)
self.assertEqual(len(list(checks.check_explicit_underscore_import(
"msg = _('My message')",
"masakarimonitors/tests/other_files3.py"))), 0)

12
tox.ini
View File

@ -35,9 +35,17 @@ commands =
commands = oslo_debug_helper {posargs}
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
# E123 - closing bracket does not match indentation of opening bracket's line
# E125 - continuation line with same indent as next logical line
# E128 - continuation line under-indented for visual indent
# E265 - block comment should start with '# '
# H405 - multi line docstring summary not separated with an empty line
show-source = True
ignore = E123,E125
ignore = E123,E125,E128,E265,H405
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
[hacking]
import_exceptions = masakarimonitors.i18n