diff --git a/Makefile b/Makefile index bdbe1116..2ce4b668 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,10 @@ test: docs coverage report -m [ -f "/usr/bin/coveralls" ] && [ -n "$(COVERALLS_REPO_TOKEN)" ] && coveralls || echo +check: + @echo "*** Running pylint ***" + ./tests/pylint/runpylint.sh + clean: -rm -rf build src/pylorax/version.py -rm -rf build src/composer/version.py diff --git a/tests/lib/testlib.sh b/tests/lib/testlib.sh new file mode 100644 index 00000000..896d8849 --- /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 . +# +# Author: David Shea + +# 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/pylint-false-positives b/tests/pylint/pylint-false-positives new file mode 100644 index 00000000..449a192e --- /dev/null +++ b/tests/pylint/pylint-false-positives @@ -0,0 +1,3 @@ +F0401 import-error Unable to import 'pylorax.version' +E0611 no-name-in-module No name 'version' in module 'pylorax' +E1101 no-member Module 'pylorax' has no 'version' member diff --git a/tests/pylint/pylint-one.sh b/tests/pylint/pylint-one.sh new file mode 100755 index 00000000..2c9a40c1 --- /dev/null +++ b/tests/pylint/pylint-one.sh @@ -0,0 +1,32 @@ +#!/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} {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 \ + $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 00000000..aab61d93 --- /dev/null +++ b/tests/pylint/runpylint.sh @@ -0,0 +1,139 @@ +#!/bin/bash +# This script will check the project 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 + +# XDG_RUNTIME_DIR is "required" to be set, so make one up in case something +# actually tries to do something with it +if [ -z "$XDG_RUNTIME_DIR" ]; then + export XDG_RUNTIME_DIR="$(mktemp -d)" + trap "rm -rf \"$XDG_RUNTIME_DIR\"" EXIT +fi + +# 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 + +# Force the GDK backend to X11. Otherwise if no display can be found, Gdk +# tries every backend type, which includes "broadway," which prints an error +# and keeps changing the content of said error. +export GDK_BACKEND=x11 + +# 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" + +# W0110 - map/filter on lambda could be replaced by comprehension +# W0123 - Use of eval +# 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 +# W0613 - Unused argument %r +# W0614 - Unused import %s from wildcard import +# I0011 - Locally disabling %s (i.e., pylint: disable) +# I0012 - Locally enabling %s (i.e., pylint: enable) +# I0013 - Ignoring entire file (i.e., pylint: skip-file) +export DISABLED_WARN_OPTIONS="--disable=W0110,W0123,W0141,W0142,W0511,W0603,W0613,W0614,I0011,I0012,I0013" + +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 '(|/)old_tests/') +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 00000000..48074422 --- /dev/null +++ b/tests/testenv.sh @@ -0,0 +1,19 @@ +#!/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}" + +if [ -z "$PYTHONPATH" ]; then + PYTHONPATH="${top_builddir}/src/:${top_srcdir}/tests/lib" +else + PYTHONPATH="${PYTHONPATH}:${top_srcdir}/src/:${top_srcdir}:${top_srcdir}/tests/lib" +fi + +export PYTHONPATH +export top_srcdir +export top_builddir