[blivet-gui:master 1/9] Initial pylint setup.
mulhern
amulhern at redhat.com
Wed Jan 14 16:53:34 UTC 2015
Signed-off-by: mulhern <amulhern at redhat.com>
---
.gitignore | 1 -
tests/lib/testlib.sh | 37 ++++++
tests/pylint/intl.py | 63 +++++++++
tests/pylint/pointless-override.py | 252 ++++++++++++++++++++++++++++++++++++
tests/pylint/pylint-false-positives | 0
tests/pylint/pylint-one.sh | 33 +++++
tests/pylint/runpylint.sh | 125 ++++++++++++++++++
tests/testenv.sh | 13 ++
8 files changed, 523 insertions(+), 1 deletion(-)
create mode 100644 tests/lib/testlib.sh
create mode 100644 tests/pylint/intl.py
create mode 100644 tests/pylint/pointless-override.py
create mode 100644 tests/pylint/pylint-false-positives
create mode 100755 tests/pylint/pylint-one.sh
create mode 100755 tests/pylint/runpylint.sh
create mode 100644 tests/testenv.sh
diff --git a/.gitignore b/.gitignore
index 95bcc2c..31d2fc8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,5 +22,4 @@ doc/_build
*.sqlite3
-tests
.directory
diff --git a/tests/lib/testlib.sh b/tests/lib/testlib.sh
new file mode 100644
index 0000000..896d884
--- /dev/null
+++ b/tests/lib/testlib.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+# Shell functions for use by anaconda tests
+#
+# Copyright (C) 2014 Red Hat, Inc.
+#
+# This program 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 program 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 program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: David Shea <dshea at redhat.com>
+
+# Print a list of files to test on stdout
+# Takes filter arguments identical to the find utility, for example
+# findtestfiles -name '*.py'. Note that pruning directories will not
+# work since find is passed a list of filenames as the path arguments.
+findtestfiles()
+{
+ # If the test is being run from a git work tree, use a list of all files
+ # known to git
+ if [ -d "${top_srcdir}/.git" ]; then
+ findpath=$(git ls-files -c "${top_srcdir}")
+ # Otherwise list everything under $top_srcdir
+ else
+ findpath="${top_srcdir} -type f"
+ fi
+
+ find $findpath "$@"
+}
diff --git a/tests/pylint/intl.py b/tests/pylint/intl.py
new file mode 100644
index 0000000..3052680
--- /dev/null
+++ b/tests/pylint/intl.py
@@ -0,0 +1,63 @@
+# I18N-related pylint module
+#
+# Copyright (C) 2013 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details. You should have received a copy of the
+# GNU General Public License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): Chris Lumens <clumens at redhat.com>
+#
+
+import astroid
+
+from pylint.checkers import BaseChecker
+from pylint.checkers.utils import check_messages
+from pylint.interfaces import IAstroidChecker
+
+translationMethods = ["_", "N_", "P_", "C_", "CN_", "CP_"]
+
+class IntlChecker(BaseChecker):
+ __implements__ = (IAstroidChecker, )
+ name = "internationalization"
+ msgs = {"W9901": ("Found % in a call to a _() method",
+ "found-percent-in-_",
+ "% in a call to one of the _() methods results in incorrect translations"),
+ "W9902": ("Found _ call at module/class level",
+ "found-_-in-module-class",
+ "Calling _ at the module or class level results in translations to the wrong language")
+ }
+
+ @check_messages("W9901")
+ def visit_binop(self, node):
+ if node.op != "%":
+ return
+
+ curr = node
+ while curr.parent:
+ if isinstance(curr.parent, astroid.CallFunc) and getattr(curr.parent.func, "name", "") in translationMethods:
+ self.add_message("W9901", node=node)
+ break
+
+ curr = curr.parent
+
+ @check_messages("W9902")
+ def visit_callfunc(self, node):
+ # The first test skips internal functions like getattr.
+ if isinstance(node.func, astroid.Name) and node.func.name == "_":
+ if isinstance(node.scope(), astroid.Module) or isinstance(node.scope(), astroid.Class):
+ self.add_message("W9902", node=node)
+
+def register(linter):
+ """required method to auto register this checker """
+ linter.register_checker(IntlChecker(linter))
diff --git a/tests/pylint/pointless-override.py b/tests/pylint/pointless-override.py
new file mode 100644
index 0000000..c8d4776
--- /dev/null
+++ b/tests/pylint/pointless-override.py
@@ -0,0 +1,252 @@
+# Pylint checker for pointless class attributes overrides.
+#
+# Copyright (C) 2014 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details. You should have received a copy of the
+# GNU General Public License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): Anne Mulhern <amulhern at redhat.com>
+#
+
+import abc
+
+from six import add_metaclass
+
+import astroid
+
+from pylint.checkers import BaseChecker
+from pylint.checkers.utils import check_messages
+from pylint.interfaces import IAstroidChecker
+
+ at add_metaclass(abc.ABCMeta)
+class PointlessData(object):
+
+ _DEF_CLASS = abc.abstractproperty(doc="Class of interesting definitions.")
+ message_id = abc.abstractproperty(doc="Pylint message identifier.")
+
+ @classmethod
+ @abc.abstractmethod
+ def _retain_node(cls, node, restrict=True):
+ """ Determines whether to retain a node for the analysis.
+
+ :param node: an AST node
+ :type node: astroid.Class
+ :param restrict bool: True if results returned should be restricted
+ :returns: True if the node should be kept, otherwise False
+ :rtype: bool
+
+ Restricted nodes are candidates for being marked as overridden.
+ Only restricted nodes are put into the initial pool of candidates.
+ """
+ raise NotImplementedError()
+
+ @staticmethod
+ @abc.abstractmethod
+ def _extract_value(node):
+ """ Return the node that contains the assignment's value.
+
+ :param node: an AST node
+ :type node: astroid.Class
+ :returns: the node corresponding to the value
+ :rtype: bool
+ """
+ raise NotImplementedError()
+
+ @staticmethod
+ @abc.abstractmethod
+ def _extract_targets(node):
+ """ Generates the names being assigned to.
+
+ :param node: an AST node
+ :type node: astroid.Class
+ :returns: a list of assignment target names
+ :rtype: generator of str
+ """
+ raise NotImplementedError()
+
+ @classmethod
+ def get_data(cls, node, restrict=True):
+ """ Find relevant nodes for this analysis.
+
+ :param node: an AST node
+ :type node: astroid.Class
+ :param restrict bool: True if results returned should be restricted
+
+ :rtype: generator of astroid.Class
+ :returns: a generator of interesting nodes.
+
+ Note that all nodes returned are guaranteed to be instances of
+ some class in self._DEF_CLASS.
+ """
+ nodes = (n for n in node.body if isinstance(n, cls._DEF_CLASS))
+ for n in nodes:
+ if cls._retain_node(n, restrict):
+ for name in cls._extract_targets(n):
+ yield (name, cls._extract_value(n))
+
+ @classmethod
+ @abc.abstractmethod
+ def check_equal(cls, node, other):
+ """ Check whether the two nodes are considered equal.
+
+ :param node: some ast node
+ :param other: some ast node
+
+ :rtype: bool
+ :returns: True if the nodes are considered equal, otherwise False
+
+ If the method returns True, the nodes are actually equal, but it
+ may return False when the nodes are equal.
+ """
+ raise NotImplementedError()
+
+class PointlessFunctionDefinition(PointlessData):
+ """ Looking for pointless function definitions. """
+
+ _DEF_CLASS = astroid.Function
+ message_id = "W9952"
+
+ @classmethod
+ def _retain_node(cls, node, restrict=True):
+ return not restrict or \
+ (len(node.body) == 1 and isinstance(node.body[0], astroid.Pass))
+
+ @classmethod
+ def check_equal(cls, node, other):
+ return len(node.body) == 1 and isinstance(node.body[0], astroid.Pass) and \
+ len(other.body) == 1 and isinstance(other.body[0], astroid.Pass)
+
+ @staticmethod
+ def _extract_value(node):
+ return node
+
+ @staticmethod
+ def _extract_targets(node):
+ yield node.name
+
+class PointlessAssignment(PointlessData):
+
+ _DEF_CLASS = astroid.Assign
+ message_id = "W9951"
+
+ _VALUE_CLASSES = (
+ astroid.Const,
+ astroid.Dict,
+ astroid.List,
+ astroid.Tuple
+ )
+
+ @classmethod
+ def _retain_node(cls, node, restrict=True):
+ return not restrict or isinstance(node.value, cls._VALUE_CLASSES)
+
+ @classmethod
+ def check_equal(cls, node, other):
+ if type(node) != type(other):
+ return False
+ if isinstance(node, astroid.Const):
+ return node.value == other.value
+ if isinstance(node, (astroid.List, astroid.Tuple)):
+ return len(node.elts) == len(other.elts) and \
+ all(cls.check_equal(n, o) for (n, o) in zip(node.elts, other.elts))
+ if isinstance(node, astroid.Dict):
+ return len(node.items) == len(other.items)
+ return False
+
+ @staticmethod
+ def _extract_value(node):
+ return node.value
+
+ @staticmethod
+ def _extract_targets(node):
+ for target in node.targets:
+ yield target.name
+
+class PointlessClassAttributeOverrideChecker(BaseChecker):
+ """ If the nearest definition of the class attribute in the MRO assigns
+ it the same value, then the overriding definition is said to be
+ pointless.
+
+ The algorithm for detecting a pointless attribute override is the following.
+
+ * For each class, C:
+ - For each attribute assignment,
+ name_1 = name_2 ... name_n = l (where l is a literal):
+ * For each n in (n_1, n_2):
+ - Traverse the linearization of the MRO until the first
+ matching assignment n = l' is identified. If l is equal to l',
+ then consider that the assignment to l in C is a
+ pointless override.
+
+ The algorithm for detecting a pointless method override has the same
+ general structure, and the same defects discussed below.
+
+ Note that this analysis is neither sound nor complete. It is unsound
+ under multiple inheritance. Consider the following class hierarchy::
+
+ class A(object):
+ _attrib = False
+
+ class B(A):
+ _attrib = False
+
+ class C(A):
+ _attrib = True
+
+ class D(B,C):
+ pass
+
+ In this case, starting from B, B._attrib = False would be considered
+ pointless. However, for D the MRO is B, C, A, and removing the assignment
+ B._attrib = False would change the inherited value of D._attrib from
+ False to True.
+
+ The analysis is incomplete because it will find some values unequal when
+ actually they are equal.
+
+ The analysis is both incomplete and unsound because it expects that
+ assignments will always be made by means of the same syntax.
+ """
+
+ __implements__ = (IAstroidChecker,)
+
+ name = "pointless class attribute override checker"
+ msgs = {
+ "W9951":
+ (
+ "Assignment to class attribute %s overrides identical assignment in ancestor.",
+ "pointless-class-attribute-override",
+ "Assignment to class attribute that overrides assignment in ancestor that assigns identical value has no effect."
+ ),
+ "W9952":
+ (
+ "definition of %s method overrides identical method definition in ancestor",
+ "pointless-method-definition-override",
+ "Overriding empty method definition with another empty method definition has no effect."
+ )
+ }
+
+ @check_messages("W9951", "W9952")
+ def visit_class(self, node):
+ for checker in (PointlessAssignment, PointlessFunctionDefinition):
+ for (name, value) in checker.get_data(node):
+ for a in node.ancestors():
+ match = next((v for (n, v) in checker.get_data(a, False) if n == name), None)
+ if match is not None:
+ if checker.check_equal(value, match):
+ self.add_message(checker.message_id, node=value, args=(name,))
+ break
+
+def register(linter):
+ linter.register_checker(PointlessClassAttributeOverrideChecker(linter))
diff --git a/tests/pylint/pylint-false-positives b/tests/pylint/pylint-false-positives
new file mode 100644
index 0000000..e69de29
diff --git a/tests/pylint/pylint-one.sh b/tests/pylint/pylint-one.sh
new file mode 100755
index 0000000..9b1e79b
--- /dev/null
+++ b/tests/pylint/pylint-one.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# $1 -- python source to run pylint on
+#
+
+if [ $# -lt 1 ]; then
+ # no source, just exit
+ exit 1
+fi
+
+file_suffix="$(eval echo \$$#|sed s?/?_?g)"
+
+pylint_output="$(pylint \
+ --msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}' \
+ -r n --disable=C,R --rcfile=/dev/null \
+ --dummy-variables-rgx=_ \
+ --ignored-classes=Popen,TransactionSet \
+ --defining-attr-methods=__init__,_grabObjects,initialize,reset,start,setUp \
+ --load-plugins=intl,pointless-override \
+ $DISABLED_WARN_OPTIONS \
+ $DISABLED_ERR_OPTIONS \
+ $NON_STRICT_OPTIONS "$@" 2>&1 | \
+ egrep -v -f "$FALSE_POSITIVES" \
+ )"
+
+# I0011 is the informational "Locally disabling ...." message
+if [ -n "$(echo "$pylint_output" | fgrep -v '************* Module ' |\
+ grep -v '^I0011:')" ]; then
+ # Replace the Module line with the actual filename
+ pylint_output="$(echo "$pylint_output" | sed "s|\* Module .*|* Module $(eval echo \$$#)|")"
+ echo "$pylint_output" > pylint-out_$file_suffix
+ touch "pylint-$file_suffix-failed"
+fi
diff --git a/tests/pylint/runpylint.sh b/tests/pylint/runpylint.sh
new file mode 100755
index 0000000..18200bf
--- /dev/null
+++ b/tests/pylint/runpylint.sh
@@ -0,0 +1,125 @@
+#!/bin/bash
+
+# This script will check for any pylint warning and errors using a set
+# of options minimizing false positives, in combination with filtering of any
+# warning regularexpressions listed in pylint-false-positives.
+#
+# If any warnings are found they will be stored in pylint-log and printed
+# to stdout and this script will exit with a status of 1, if no (non filtered)
+# warnings are found it exits with a status of 0
+
+# If $top_srcdir is set, assume this is being run from automake and we don't
+# need to keep a separate log
+export pylint_log=0
+if [ -z "$top_srcdir" ]; then
+ export pylint_log=1
+fi
+
+# Unset TERM so that things that use readline don't output terminal garbage
+unset TERM
+
+# Don't try to connect to the accessibility socket
+export NO_AT_BRIDGE=1
+
+# If $top_srcdir has not been set by automake, import the test environment
+if [ -z "$top_srcdir" ]; then
+ top_srcdir="$(dirname "$0")/../.."
+ . ${top_srcdir}/tests/testenv.sh
+fi
+
+. ${top_srcdir}/tests/lib/testlib.sh
+
+srcdir="${top_srcdir}/tests/pylint"
+builddir="${top_builddir}/tests/pylint"
+
+# Need to add the pylint module directory to PYTHONPATH as well.
+export PYTHONPATH="${PYTHONPATH}:${srcdir}"
+
+# Save analysis data in the pylint directory
+export PYLINTHOME="${builddir}/.pylint.d"
+[ -d "$PYLINTHOME" ] || mkdir "$PYLINTHOME"
+
+export FALSE_POSITIVES="${srcdir}"/pylint-false-positives
+
+# W0212 - Access to a protected member %s of a client class
+export NON_STRICT_OPTIONS="--disable=W0212"
+
+# E1103 - %s %r has no %r member (but some types could not be inferred)
+export DISABLED_ERR_OPTIONS="--disable=E1103"
+
+# W0105 - String statement has no effect
+# W0110 - map/filter on lambda could be replaced by comprehension
+# W0141 - Used builtin function %r
+# W0142 - Used * or ** magic
+# W0511 - Used when a warning note as FIXME or XXX is detected.
+# W0603 - Using the global statement
+# W0614 - Unused import %s from wildcard import
+# I0011 - Locally disabling %s (i.e., pylint: disable)
+export DISABLED_WARN_OPTIONS="--disable=W0105,W0110,W0141,W0142,W0511,W0603,W0614,I0011"
+
+usage () {
+ echo "usage: `basename $0` [--strict] [--help] [files...]"
+ exit $1
+}
+
+# Separate the module parameters from the files list
+ARGS=
+FILES=
+while [ $# -gt 0 ]; do
+ case $1 in
+ --strict)
+ export NON_STRICT_OPTIONS=
+ ;;
+ --help)
+ usage 0
+ ;;
+ -*)
+ ARGS="$ARGS $1"
+ ;;
+ *)
+ FILES=$@
+ break
+ esac
+ shift
+done
+
+exit_status=0
+
+if [ -s pylint-log ]; then
+ rm pylint-log
+fi
+
+# run pylint one file / module at a time, otherwise it sometimes gets
+# confused
+if [ -z "$FILES" ]; then
+ # Test any file that either ends in .py or contains #!/usr/bin/python in
+ # the first line. Scan everything except old_tests
+ FILES=$(findtestfiles \( -name '*.py' -o \
+ -exec /bin/sh -c "head -1 {} | grep -q '#!/usr/bin/python'" \; \) -print | \
+ egrep -v '(|/)doc/conf.py')
+fi
+
+num_cpus=$(getconf _NPROCESSORS_ONLN)
+# run pylint in paralel
+echo $FILES | xargs --max-procs=$num_cpus -n 1 "$srcdir"/pylint-one.sh $ARGS || exit 1
+
+for file in $(find -name 'pylint-out*'); do
+ cat "$file" >> pylint-log
+ rm "$file"
+done
+
+fails=$(find -name 'pylint*failed' -print -exec rm '{}' \;)
+if [ -z "$fails" ]; then
+ exit_status=0
+else
+ exit_status=1
+fi
+
+if [ -s pylint-log ]; then
+ echo "pylint reports the following issues:"
+ cat pylint-log
+elif [ -e pylint-log ]; then
+ rm pylint-log
+fi
+
+exit "$exit_status"
diff --git a/tests/testenv.sh b/tests/testenv.sh
new file mode 100644
index 0000000..fca48db
--- /dev/null
+++ b/tests/testenv.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+if [ -z "$top_srcdir" ]; then
+ echo "*** top_srcdir must be set"
+ exit 1
+fi
+
+# If no top_builddir is set, use top_srcdir
+: "${top_builddir:=$top_srcdir}"
+
+export PYTHONPATH
+export top_srcdir
+export top_builddir
--
1.9.3
More information about the anaconda-patches
mailing list