From dd28e90f183972cc2a395094ce3e3f72e861953f Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 21 Sep 2012 13:55:05 -0400 Subject: run pylint for errors on almost everything, full runs on some selected stuff --- .../Testlib/TestServer/TestPlugin/Testhelpers.py | 4 +- testsuite/Testsrc/testmisc.py | 136 +++++++++++ testsuite/pylintrc.conf | 251 +++++++++++++++++++++ 3 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 testsuite/Testsrc/testmisc.py create mode 100644 testsuite/pylintrc.conf (limited to 'testsuite') diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py index 5970d0e9d..aea00c356 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py @@ -1705,7 +1705,7 @@ class TestEntrySet(TestDebuggable): "bogus: line"] mock_open.return_value.readlines.return_value = idata eset.update_metadata(event) - expected = default_file_metadata.copy() + expected = DEFAULT_FILE_METADATA.copy() expected['owner'] = 'owner' expected['group'] = 'GROUP' expected['perms'] = '0775' @@ -1728,7 +1728,7 @@ class TestEntrySet(TestDebuggable): event.filename = fname eset.metadata = Mock() eset.reset_metadata(event) - self.assertItemsEqual(eset.metadata, default_file_metadata) + self.assertItemsEqual(eset.metadata, DEFAULT_FILE_METADATA) @patch("Bcfg2.Server.Plugin.helpers.bind_info") def test_bind_info_to_entry(self, mock_bind_info): diff --git a/testsuite/Testsrc/testmisc.py b/testsuite/Testsrc/testmisc.py new file mode 100644 index 000000000..5f84d00bf --- /dev/null +++ b/testsuite/Testsrc/testmisc.py @@ -0,0 +1,136 @@ +import os +import re +import sys +import glob +from subprocess import Popen, PIPE, STDOUT + +# add all parent testsuite directories to sys.path to allow (most) +# relative imports in python 2.4 +_path = os.path.dirname(__file__) +while _path != '/': + if os.path.basename(_path).lower().startswith("test"): + sys.path.append(_path) + if os.path.basename(_path) == "testsuite": + break + _path = os.path.dirname(_path) +from common import can_skip, skip, skipIf, skipUnless, Bcfg2TestCase + +try: + import django + HAS_DJANGO = True +except ImportError: + HAS_DJANGO = False + +# path to Bcfg2 src directory +srcpath = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", + "src")) + +# path to pylint rc file +rcfile = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", + "pylintrc.conf")) + + +class TestPylint(Bcfg2TestCase): + # right now, too many things fail pylint miserably to just test + # everything, or even to do a blacklist, so we just whitelist the + # things we do want to do a full check on and only check most + # stuff for errors and fatal errors. This is a dict of + # => . + # is relative to src/ + whitelist = { + "lib/Bcfg2/Server": ["Plugin"], + "lib/Bcfg2/Server/Plugins": ["PuppetENC.py", + "Rules.py", + "DBStats.py", + "Trigger.py", + "Defaults.py", + "Probes.py", + "TemplateHelper.py", + "Guppy.py", + "FileProbes.py", + "ServiceCompat.py", + "Properties.py", + "SEModules.py", + "Darcs.py", + "Git.py", + "Hg.py", + "Cvs.py", + "Fossil.py", + "Svn.py", + "Svn2.py", + "Bzr.py", + "Cfg", + "Packages"] + } + + pylint_cmd = ["pylint", "--rcfile", rcfile] + + # regex to find errors and fatal errors + error_re = re.compile(r':\d+:\s+\[[EF]\d{4}') + + @skipIf(not os.path.exists(srcpath), "%s does not exist" % srcpath) + @skipIf(not os.path.exists(rcfile), "%s does not exist" % rcfile) + def test_pylint_full(self): + paths = [] + for parent, modules in self.whitelist.items(): + paths.extend([os.path.join(srcpath, parent, m) for m in modules]) + args = self.pylint_cmd + paths + try: + pylint = Popen(args, stdout=PIPE, stderr=STDOUT) + print(pylint.communicate()[0]) + rv = pylint.wait() + except OSError: + if can_skip: + return skip("pylint not found") + else: + print("pylint not found") + return + self.assertEqual(rv, 0) + + def test_sbin_errors(self): + return self._pylint_errors(glob.glob("sbin/*")) + + @skipUnless(HAS_DJANGO, "Django not found, skipping") + def test_django_errors(self): + return self._pylint_errors(["lib/Bcfg2/Server/Reports", + "lib/Bcfg2/Server/models.py"], + extra_args=["-d", "E1101"]) + + def test_lib_errors(self): + # we ignore stuff that uses django (Reports, Hostbase, + # models.py) or that is deprecated and raises lots of errors + # (Snapshots, Hostbase), or that just raises a lot of errors + # (APT.py, RPMng.py, rpmtools.py). Reports is tested by + # test_django_errors + ignore = ["models.py", "APT.py", "RPMng.py", "rpmtools.py", + "Snapshots", "Reports", "Hostbase"] + return self._pylint_errors(["lib/Bcfg2"], + extra_args=["--ignore", ",".join(ignore)]) + + @skipIf(not os.path.exists(srcpath), "%s does not exist" % srcpath) + @skipIf(not os.path.exists(rcfile), "%s does not exist" % rcfile) + def _pylint_errors(self, paths, extra_args=None): + """ test all files for fatals and errors """ + if extra_args is None: + extra_args = [] + args = self.pylint_cmd + extra_args + \ + ["-f", "parseable", "-d", "R0801,E1103"] + \ + [os.path.join(srcpath, p) for p in paths] + try: + pylint = Popen(args, stdout=PIPE, stderr=STDOUT) + output = pylint.communicate()[0] + rv = pylint.wait() + except OSError: + if can_skip: + return skip("pylint not found") + else: + print("pylint not found") + return + + for line in output.splitlines(): + #print line + if self.error_re.search(line): + print(line) + # pylint returns a bitmask, where 1 means fatal errors + # were encountered and 2 means errors were encountered. + self.assertEqual(rv & 3, 0) diff --git a/testsuite/pylintrc.conf b/testsuite/pylintrc.conf new file mode 100644 index 000000000..29e35135c --- /dev/null +++ b/testsuite/pylintrc.conf @@ -0,0 +1,251 @@ +[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=no + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[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=W0142,W0511,W0603,R0201,R0901,R0903,R0904,R0921,R0922,C0302,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=no + +# 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=6 + +# 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=79 + +# 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=' ' + + +[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,objects,DoesNotExist + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[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]+))$ + +# 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]+$ + +# 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. Change the ranges below to [a-z] when ready to make all API +# methods consistent. +method-rgx=[A-z_][A-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=(Entries|[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=(rv|el|fd|[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=__.*__ + + +[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= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=6 + +# 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=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=18 + +# Maximum number of statements in function / method body +max-statements=75 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=15 + +# 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=25 + + +[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 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception -- cgit v1.2.3-1-g7c22