diff --git a/HACKING.rst b/HACKING.rst index 59a474d..0f1b933 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -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. diff --git a/masakarimonitors/hacking/__init__.py b/masakarimonitors/hacking/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/masakarimonitors/hacking/checks.py b/masakarimonitors/hacking/checks.py new file mode 100644 index 0000000..ec03267 --- /dev/null +++ b/masakarimonitors/hacking/checks.py @@ -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) diff --git a/masakarimonitors/tests/unit/test_hacking.py b/masakarimonitors/tests/unit/test_hacking.py new file mode 100644 index 0000000..5dee83d --- /dev/null +++ b/masakarimonitors/tests/unit/test_hacking.py @@ -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) diff --git a/tox.ini b/tox.ini index 68ec07a..a3bd124 100644 --- a/tox.ini +++ b/tox.ini @@ -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 +