diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/pylint/README | 36 | ||||
-rw-r--r-- | tools/pylint/logilab-modutils-0.57.1.patch | 22 | ||||
-rw-r--r-- | tools/pylint/plugins/__init__.py | 20 | ||||
-rw-r--r-- | tools/pylint/plugins/cim_provider_checker.py | 149 | ||||
-rw-r--r-- | tools/pylint/plugins/unittest_checker.py | 82 | ||||
-rwxr-xr-x | tools/pylint/pylintlmi | 8 | ||||
-rw-r--r-- | tools/pylint/pylintrc | 249 |
7 files changed, 566 insertions, 0 deletions
diff --git a/tools/pylint/README b/tools/pylint/README new file mode 100644 index 0000000..0f30dc2 --- /dev/null +++ b/tools/pylint/README @@ -0,0 +1,36 @@ +This tool uses pylint to check for errors in python code. +Before using it, please ensure, that you have installed openlmi-python +and all of the other cim provider packages, that you want to chek, into +your PYTHONPATH. + +INSTALLATION TO PYTHON PATH + * If checking already installed providers under /usr/lib/pythonX.Y/* + you may skip this step. + 1. Choose some directory for your PYTHONPATH, if you don't have one yet. eg: + $ mkdir ~/.python_sandbox + $ export PYTHONPATH=~/.python_sandbox + note: you may put the export line into you .profile + 2. Go to the directory of python egg, you want to install and install it: + $ cd openlmi-providers/src/python + $ python setup.py develop --install-dir=$PYTHONPATH + 3. Repeat step 2 for providers you want to check. + 4. Check sources. + +FIX FOR PYLINT + * Due to a bug in pylint's module handling, it fails to work with namespace + packages. A bug is described here: http://www.logilab.org/ticket/8796 + * To fix this you may apply attached patch "logilab-modutils-0.57.1.patch" + to logilab module (this one is for version 0.57.1): + $ patch -d /usr/lib/python2.7/site-packages/logilab -p1 \ + <logilab-modutils-0.57.1.patch + +RUNNING PYLINT CHECKER + 1. Add pylint directory to your PATH: + $ cd openlmi-providers + $ export PATH=`pwd`/tools/pylint:"$PATH" + 2. Use it + * Run it on single module: + $ cd src/software + $ pylintlmi openlmi/software/LMI_SoftwarePackage.py + * Run it on whole package: + $ pylintlmi openlmi diff --git a/tools/pylint/logilab-modutils-0.57.1.patch b/tools/pylint/logilab-modutils-0.57.1.patch new file mode 100644 index 0000000..6cae893 --- /dev/null +++ b/tools/pylint/logilab-modutils-0.57.1.patch @@ -0,0 +1,22 @@ +Index: logilab/common/modutils.py +=================================================================== +--- logilab.orig/common/modutils.py ++++ logilab/common/modutils.py +@@ -34,6 +34,7 @@ import os + from os.path import splitext, join, abspath, isdir, dirname, exists, basename + from imp import find_module, load_module, C_BUILTIN, PY_COMPILED, PKG_DIRECTORY + from distutils.sysconfig import get_config_var, get_python_lib, get_python_version ++import pkg_resources + + try: + import zipimport +@@ -601,6 +602,9 @@ def _module_file(modpath, path=None): + checkeggs = False + imported = [] + while modpath: ++ if modpath[0] in pkg_resources._namespace_packages and len(modpath) > 1: ++ module = sys.modules[modpath.pop(0)] ++ path = module.__path__ + try: + _, mp_filename, mp_desc = find_module(modpath[0], path) + except ImportError: diff --git a/tools/pylint/plugins/__init__.py b/tools/pylint/plugins/__init__.py new file mode 100644 index 0000000..2ebe827 --- /dev/null +++ b/tools/pylint/plugins/__init__.py @@ -0,0 +1,20 @@ +# Software Management Providers +# +# Copyright (C) 2012 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# diff --git a/tools/pylint/plugins/cim_provider_checker.py b/tools/pylint/plugins/cim_provider_checker.py new file mode 100644 index 0000000..253cb9d --- /dev/null +++ b/tools/pylint/plugins/cim_provider_checker.py @@ -0,0 +1,149 @@ +# -*- encoding: utf-8 -*- +# Software Management Providers +# +# Copyright (C) 2012 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +""" +Pylint checker for CIM provider modules and classes. +""" + +import re +from logilab.astng import node_classes # pylint: disable=W0403 +from logilab.astng import scoped_nodes # pylint: disable=W0403 +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker + +_RE_PROVIDER_CLASS_NAME = re.compile( + r'^(?P<prefix>[A-Z][a-zA-Z0-9]*)_(?P<name>[A-Z][a-zA-Z]*)$') +_RE_PROVIDER_MODULE_NAME = re.compile( + r'^((?P<parent>.*)\.)?(?P<basename>(?P<prefix>[A-Z][a-zA-Z0-9]*)' + r'_(?P<name>[A-Z][a-zA-Z]*))$') +_RE_COMMON_MODULE_NAME = re.compile( + r'^([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)$') + +def supress_cim_provider_messages(linter, node): + """ + Supress some warnings for CIMProvider2 subclass. + @param node is a subclass of CIMProvider2 + """ + assert isinstance(node, scoped_nodes.Class) + if ( 'Values' in node + and isinstance(node['Values'], scoped_nodes.Class)): + linter.disable('R0903', scope='module', line=node['Values'].lineno) + linter.disable('C0111', scope='module', line=node['Values'].lineno) + for child in node['Values'].get_children(): + if not isinstance(child, scoped_nodes.Class): + continue + linter.disable('R0903', scope='module', line=child.lineno) + linter.disable('C0111', scope='module', line=child.lineno) + +class CIMProviderChecker(BaseChecker): + """ + Checks for compliance to naming conventions for python cim providers. + """ + + __implements__ = IASTNGChecker + + name = 'cim_provider' + msgs = { + 'C9904': ('Invalid provider class name %s', + "Class name representing cim provider should be in inform " + "<prefix>_<name>. Where <prefix> and <name> should be both " + "written in CamelCase."), + 'C9905': ('Invalid provider module name %s', + "Module containing cim provider(s) should be named as " + "<prefix>_<name>. Where both <prefix> and <name> are " + "CamelCased strings."), + 'C9906': ('Prefixes of module and class does not match: %s != %s', + "Module prefix has to match provider class prefix."), + 'E9907': ("Missing get_providers function in module %s", + "Provider module must contain get_providers function."), + 'W9908': ("get_providers in module %s is not a function", + "get_providers should be a callable function."), + 'W9909': ("Missing provider name \"%s\" in providers dictionary.", + "Function get_providers returns providers association" + " dictionary, that must include all module's provider" + " classes."), + 'C9910': ("Prefix different from LMI: %s", + "All OpenLMI CIM classes have LMI_ ORGID prefix."), + 'C9911': ("Invalid module name: %s", + "All modules, that does not declare cim provider class" + " should be named according to this regexp:" + " '^(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$'."), + } + + # this is important so that your checker is executed before others + priority = -2 + + def visit_class(self, node): + """ + Check every class, which inherits from + pywbem.cim_provider2.CIMProvider2. + """ + if "CIMProvider2" in [a.name for a in node.ancestors()]: + clsm = _RE_PROVIDER_CLASS_NAME.match(node.name) + if not clsm: + self.add_message('C9904', node=node, args=node.name) + parent = node.parent + while not isinstance(parent, scoped_nodes.Module): + parent = parent.parent + modm = _RE_PROVIDER_MODULE_NAME.match(parent.name) + if not modm: + self.add_message('C9905', node=node, args=parent.name) + if not clsm: + return + if clsm.group('prefix') != modm.group('prefix'): + self.add_message('C9906', node=node, + args=(modm.group('prefix'), clsm.group('prefix'))) + if clsm.group('prefix') != 'LMI': + self.add_message('C9910', node=node, args=clsm.group('prefix')) + if not 'get_providers' in parent.keys(): + self.add_message('E9907', node=parent, args=parent.name) + return + getprovs = parent['get_providers'] + if not isinstance(getprovs, scoped_nodes.Function): + self.add_message('W9908', node=getprovs, args=parent.name) + ret = getprovs.last_child() + if not isinstance(ret, node_classes.Return): + return + dictionary = ret.get_children().next() + if not isinstance(dictionary, node_classes.Dict): + return + if not node.name in [i[0].value for i in dictionary.items]: + self.add_message('W9909', node=getprovs, args=node.name) + supress_cim_provider_messages(self.linter, node) + + def visit_module(self, node): + """ + Check for invalid module name. + """ + modm = _RE_PROVIDER_MODULE_NAME.match(node.name) + if modm: + if not _RE_COMMON_MODULE_NAME.match(node.name): + for child in node.get_children(): + if ( isinstance(child, scoped_nodes.Class) + and 'CIMProvider2' in [ + a.name for a in child.ancestors()]): + break + else: + self.add_message('C9911', node=node, args=node.name) + +def register(linter): + """required method to auto register our checker""" + linter.register_checker(CIMProviderChecker(linter)) + diff --git a/tools/pylint/plugins/unittest_checker.py b/tools/pylint/plugins/unittest_checker.py new file mode 100644 index 0000000..da9e6e7 --- /dev/null +++ b/tools/pylint/plugins/unittest_checker.py @@ -0,0 +1,82 @@ +# -*- encoding: utf-8 -*- +# Copyright (C) 2012 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Authors: Michal Minar <miminar@redhat.com> +# +"""Pylint plugin to check OpenLMI unittest modules.""" + +from logilab.astng import scoped_nodes +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker +import unittest + +def is_test_case_method(node): + """ + Check, whether node method is defined by TestCase base class. + """ + if ( isinstance(node, scoped_nodes.Function) + and node.name in unittest.TestCase.__dict__): + return True + return False + +def is_test_case_subclass(node): + """ + Check, whether node is a subclass of TestCase base class. + """ + if not isinstance(node, scoped_nodes.Class): + return False + for ancestor in node.ancestors(): + if ( ancestor.name == 'TestCase' + and ancestor.getattr('__module__')[0].value.startswith('unittest')): + return True + return False + +class TestCaseChecker(BaseChecker): + """ + Checker for OpenLMI unittest modules. + Right now it just suppresses unwanted messages. + """ + + __implements__ = IASTNGChecker + + name = 'unittest_case' + # When we do have some real warning messages/reports, + # remote W9920. We need it just to get registered. + msgs = { 'W9920': ('Dummy', "This is a dummy message.")} + + priority = -1 + + def visit_class(self, node): + """ + Suppress Naming and 'Attribute creation out of __init__' + errors for every TestCase subclass. + """ + if not is_test_case_subclass(node): + return + if 'setUp' in node: + self.linter.disable('W0201', + scope='module', line=node['setUp'].lineno) + for child in node.get_children(): + if not is_test_case_method(child): + continue + self.linter.disable('C0103', + scope='module', line=child.lineno) + +def register(linter): + """required method to auto register our checker""" + linter.register_checker(TestCaseChecker(linter)) + diff --git a/tools/pylint/pylintlmi b/tools/pylint/pylintlmi new file mode 100755 index 0000000..4e39aeb --- /dev/null +++ b/tools/pylint/pylintlmi @@ -0,0 +1,8 @@ +#!/bin/sh + +dir=`dirname $0` +[[ -z "$dir" ]] && dir=. + +export PYLINTRC=$dir/pylintrc +export PYTHONPATH="$PYTHONPATH:$dir/plugins" +exec pylint $@ diff --git a/tools/pylint/pylintrc b/tools/pylint/pylintrc new file mode 100644 index 0000000..f065e65 --- /dev/null +++ b/tools/pylint/pylintrc @@ -0,0 +1,249 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins=unittest_checker,cim_provider_checker + + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). +disable=I0011 + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=text + +# Include message's id in output +include-ids=yes + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)|([A-Z][a-zA-Z]*_[A-Z][a-zA-Z0-9]*))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=(([A-Z_][a-zA-Z0-9]+)|([A-Z][a-zA-Z]*_[A-Z][a-zA-Z0-9]*))$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=8 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception |