"Fossies" - the Fresh Open Source Software Archive

Member "barbican-12.0.0/barbican/hacking/checks.py" (14 Apr 2021, 7978 Bytes) of package /linux/misc/openstack/barbican-12.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "checks.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 11.0.0_vs_12.0.0.

    1 # Copyright (c) 2016, GohighSec
    2 # All Rights Reserved.
    3 #
    4 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    5 # not use this file except in compliance with the License. You may obtain
    6 # a copy of the License at
    7 #
    8 #      http://www.apache.org/licenses/LICENSE-2.0
    9 #
   10 # Unless required by applicable law or agreed to in writing, software
   11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   13 # License for the specific language governing permissions and limitations
   14 # under the License.
   15 
   16 import ast
   17 import re
   18 import six
   19 
   20 from hacking import core
   21 import pycodestyle
   22 
   23 
   24 """
   25 Guidelines for writing new hacking checks
   26 
   27  - Use only for Barbican specific tests. OpenStack general tests
   28    should be submitted to the common 'hacking' module.
   29  - Pick numbers in the range B3xx. Find the current test with
   30    the highest allocated number and then pick the next value.
   31  - Keep the test method code in the source file ordered based
   32    on the B3xx value.
   33  - List the new rule in the top level HACKING.rst file
   34  - Add test cases for each new rule to barbican/tests/test_hacking.py
   35 
   36 """
   37 
   38 oslo_namespace_imports = re.compile(r"from[\s]*oslo[.](.*)")
   39 dict_constructor_with_list_copy_re = re.compile(r".*\bdict\((\[)?(\(|\[)")
   40 assert_no_xrange_re = re.compile(r"\s*xrange\s*\(")
   41 assert_True = re.compile(r".*assertEqual\(True, .*\)")
   42 assert_None = re.compile(r".*assertEqual\(None, .*\)")
   43 assert_Not_Equal = re.compile(r".*assertNotEqual\(None, .*\)")
   44 assert_Is_Not = re.compile(r".*assertIsNot\(None, .*\)")
   45 no_log_warn = re.compile(r".*LOG.warn\(.*\)")
   46 
   47 
   48 class BaseASTChecker(ast.NodeVisitor):
   49     """Provides a simple framework for writing AST-based checks.
   50 
   51     Subclasses should implement visit_* methods like any other AST visitor
   52     implementation. When they detect an error for a particular node the
   53     method should call ``self.add_error(offending_node)``. Details about
   54     where in the code the error occurred will be pulled from the node
   55     object.
   56 
   57     Subclasses should also provide a class variable named CHECK_DESC to
   58     be used for the human readable error message.
   59 
   60     """
   61 
   62     CHECK_DESC = 'No check message specified'
   63 
   64     def __init__(self, tree, filename):
   65         """This object is created automatically by pycodestyle.
   66 
   67         :param tree: an AST tree
   68         :param filename: name of the file being analyzed
   69                          (ignored by our checks)
   70         """
   71         self._tree = tree
   72         self._errors = []
   73 
   74     def run(self):
   75         """Called automatically by pycodestyle."""
   76         self.visit(self._tree)
   77         return self._errors
   78 
   79     def add_error(self, node, message=None):
   80         """Add an error caused by a node to the list of errors for pep8."""
   81 
   82         message = message or self.CHECK_DESC
   83         error = (node.lineno, node.col_offset, message, self.__class__)
   84         self._errors.append(error)
   85 
   86     def _check_call_names(self, call_node, names):
   87         if isinstance(call_node, ast.Call):
   88             if isinstance(call_node.func, ast.Name):
   89                 if call_node.func.id in names:
   90                     return True
   91         return False
   92 
   93 
   94 class CheckLoggingFormatArgs(BaseASTChecker):
   95     """Check for improper use of logging format arguments.
   96 
   97     LOG.debug("Volume %s caught fire and is at %d degrees C and climbing.",
   98               ('volume1', 500))
   99 
  100     The format arguments should not be a tuple as it is easy to miss.
  101 
  102     """
  103     name = "check_logging_format_args"
  104     version = "1.0"
  105 
  106     CHECK_DESC = 'B310 Log method arguments should not be a tuple.'
  107     LOG_METHODS = [
  108         'debug', 'info',
  109         'warn', 'warning',
  110         'error', 'exception',
  111         'critical', 'fatal',
  112         'trace', 'log'
  113     ]
  114 
  115     def _find_name(self, node):
  116         """Return the fully qualified name or a Name or Attribute."""
  117         if isinstance(node, ast.Name):
  118             return node.id
  119         elif (isinstance(node, ast.Attribute)
  120                 and isinstance(node.value, (ast.Name, ast.Attribute))):
  121             method_name = node.attr
  122             obj_name = self._find_name(node.value)
  123             if obj_name is None:
  124                 return None
  125             return obj_name + '.' + method_name
  126         elif isinstance(node, six.string_types):
  127             return node
  128         else:  # could be Subscript, Call or many more
  129             return None
  130 
  131     def visit_Call(self, node):
  132         """Look for the 'LOG.*' calls."""
  133         # extract the obj_name and method_name
  134         if isinstance(node.func, ast.Attribute):
  135             obj_name = self._find_name(node.func.value)
  136             if isinstance(node.func.value, ast.Name):
  137                 method_name = node.func.attr
  138             elif isinstance(node.func.value, ast.Attribute):
  139                 obj_name = self._find_name(node.func.value)
  140                 method_name = node.func.attr
  141             else:  # could be Subscript, Call or many more
  142                 return super(CheckLoggingFormatArgs, self).generic_visit(node)
  143 
  144             # obj must be a logger instance and method must be a log helper
  145             if (obj_name != 'LOG'
  146                     or method_name not in self.LOG_METHODS):
  147                 return super(CheckLoggingFormatArgs, self).generic_visit(node)
  148 
  149             # the call must have arguments
  150             if not len(node.args):
  151                 return super(CheckLoggingFormatArgs, self).generic_visit(node)
  152 
  153             # any argument should not be a tuple
  154             for arg in node.args:
  155                 if isinstance(arg, ast.Tuple):
  156                     self.add_error(arg)
  157 
  158         return super(CheckLoggingFormatArgs, self).generic_visit(node)
  159 
  160 
  161 @core.flake8ext
  162 def check_oslo_namespace_imports(physical_line, logical_line, filename):
  163     """'oslo_' should be used instead of 'oslo.'
  164 
  165     B317
  166     """
  167     if pycodestyle.noqa(physical_line):
  168         return
  169     if re.match(oslo_namespace_imports, logical_line):
  170         msg = ("B317: '%s' must be used instead of '%s'.") % (
  171             logical_line.replace('oslo.', 'oslo_'),
  172             logical_line)
  173         yield(0, msg)
  174 
  175 
  176 @core.flake8ext
  177 def dict_constructor_with_list_copy(logical_line):
  178     """Use a dict comprehension instead of a dict constructor
  179 
  180     B318
  181     """
  182     msg = ("B318: Must use a dict comprehension instead of a dict constructor"
  183            " with a sequence of key-value pairs."
  184            )
  185     if dict_constructor_with_list_copy_re.match(logical_line):
  186         yield (0, msg)
  187 
  188 
  189 @core.flake8ext
  190 def no_xrange(logical_line):
  191     """Do not use 'xrange'
  192 
  193     B319
  194     """
  195     if assert_no_xrange_re.match(logical_line):
  196         yield(0, "B319: Do not use xrange().")
  197 
  198 
  199 @core.flake8ext
  200 def validate_assertTrue(logical_line):
  201     """Use 'assertTrue' instead of 'assertEqual'
  202 
  203     B312
  204     """
  205     if re.match(assert_True, logical_line):
  206         msg = ("B312: Unit tests should use assertTrue(value) instead"
  207                " of using assertEqual(True, value).")
  208         yield(0, msg)
  209 
  210 
  211 @core.flake8ext
  212 def validate_assertIsNone(logical_line):
  213     """Use 'assertIsNone' instead of 'assertEqual'
  214 
  215     B311
  216     """
  217     if re.match(assert_None, logical_line):
  218         msg = ("B311: Unit tests should use assertIsNone(value) instead"
  219                " of using assertEqual(None, value).")
  220         yield(0, msg)
  221 
  222 
  223 @core.flake8ext
  224 def no_log_warn_check(logical_line):
  225     """Disallow 'LOG.warn'
  226 
  227     B320
  228     """
  229     msg = ("B320: LOG.warn is deprecated, please use LOG.warning!")
  230     if re.match(no_log_warn, logical_line):
  231         yield(0, msg)
  232 
  233 
  234 @core.flake8ext
  235 def validate_assertIsNotNone(logical_line):
  236     """Use 'assertIsNotNone'
  237 
  238     B321
  239     """
  240     if re.match(assert_Not_Equal, logical_line) or \
  241        re.match(assert_Is_Not, logical_line):
  242         msg = ("B321: Unit tests should use assertIsNotNone(value) instead"
  243                " of using assertNotEqual(None, value) or"
  244                " assertIsNot(None, value).")
  245         yield(0, msg)