From 9a08e00548d1414f4a4ed3901ed4b4dc817c3f1a Mon Sep 17 00:00:00 2001 From: Joe Gordon Date: Fri, 20 Jan 2012 00:50:56 -0800 Subject: Add HACKING compliance testing to run_test.sh Tests so far: N101 TODO format N201 Except format N301 One import per line N302 import only modules N303 Invalid Import N304 Relative Import Change-Id: I33c021b842e7199b1f5f1f699ea17f7fa5f8ca49 --- run_tests.sh | 24 ++++++++ tools/hacking.py | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100755 tools/hacking.py diff --git a/run_tests.sh b/run_tests.sh index 112a55ca5..e3cf69a36 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -15,6 +15,7 @@ function usage { echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -p, --pep8 Just run pep8" echo " -P, --no-pep8 Don't run pep8" + echo " -H, --hacking Just run HACKING compliance testing" echo " -c, --coverage Generate coverage report" echo " -h, --help Print this usage message" echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list" @@ -36,6 +37,7 @@ function process_option { -f|--force) force=1;; -p|--pep8) just_pep8=1;; -P|--no-pep8) no_pep8=1;; + -H|--hacking) just_hacking=1;; -c|--coverage) coverage=1;; -*) noseopts="$noseopts $1";; *) noseargs="$noseargs $1" @@ -54,6 +56,7 @@ noseopts= wrapper="" just_pep8=0 no_pep8=0 +just_hacking=0 coverage=0 recreate_db=1 @@ -116,6 +119,21 @@ function run_pep8 { ${wrapper} pep8 ${pep8_opts} ${srcfiles} } +function run_hacking { + echo "Running hacking compliance testing..." + # Opt-out files from pep8 + ignore_scripts="*.sh:*nova-debug:*clean-vlans:*.swp" + ignore_files="*eventlet-patch:*pip-requires" + ignore_dirs="*ajaxterm*" + GLOBIGNORE="$ignore_scripts:$ignore_files:$ignore_dirs" + srcfiles=`find bin -type f ! -name "nova.conf*"` + srcfiles+=" `find tools/*`" + srcfiles+=" nova setup.py plugins/xenserver/xenapi/etc/xapi.d/plugins/glance" + hacking_opts="--ignore=E202,W602 --repeat" + ${wrapper} python tools/hacking.py ${hacking_opts} ${srcfiles} +} + + NOSETESTS="python nova/testing/runner.py $noseopts $noseargs" if [ $never_venv -eq 0 ] @@ -154,6 +172,12 @@ if [ $just_pep8 -eq 1 ]; then exit fi +if [ $just_hacking -eq 1 ]; then + run_hacking + exit +fi + + if [ $recreate_db -eq 1 ]; then rm -f tests.sqlite fi diff --git a/tools/hacking.py b/tools/hacking.py new file mode 100755 index 000000000..19317ef8f --- /dev/null +++ b/tools/hacking.py @@ -0,0 +1,183 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2012, Cloudscaling +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""nova HACKING file compliance testing + +built on top of pep8.py +""" + +import inspect +import os +import re +import sys +import traceback + +import pep8 + +#N1xx comments +#N2xx except +#N3xx imports +#N4xx docstrings +#N5xx dictionaries/lists +#N6xx Calling methods + + +def nova_todo_format(physical_line): + """ + nova HACKING guide recommendation for TODO: + Include your name with TODOs as in "#TODO(termie)" + N101 + """ + pos = physical_line.find('TODO') + pos1 = physical_line.find('TODO(') + pos2 = physical_line.find('#') # make sure its a comment + if (pos != pos1 and pos2 >= 0 and pos2 < pos): + return pos, "NOVA N101: Use TODO(NAME)" + + +def nova_except_format(logical_line): + """ + nova HACKING guide recommends not using except: + Do not write "except:", use "except Exception:" at the very least + N201 + """ + if logical_line.startswith("except:"): + return 6, "NOVA N201: no 'except:' at least use 'except Exception:'" + + +def nova_one_import_per_line(logical_line): + """ + nova HACKING guide recommends one import per line: + Do not import more than one module per line + + Examples: + BAD: from nova.rpc.common import RemoteError, LOG + BAD: from sqlalchemy import MetaData, Table + N301 + """ + pos = logical_line.find(',') + if (pos > -1 and (logical_line.startswith("import ") or + (logical_line.startswith("from ") and + logical_line.split()[2] == "import"))): + return pos, "NOVA N301: one import per line" + + +def nova_import_module_only(logical_line): + """ + nova HACKING guide recommends importing only modules: + Do not import objects, only modules + N302 import only modules + N303 Invalid Import + N304 Relative Import + """ + def importModuleCheck(mod, parent=None, added=False): + """ + If can't find module on first try, recursively check for relative + imports + """ + current_path = os.path.dirname(pep8.current_file) + try: + valid = True + if parent: + parent_mod = __import__(parent, globals(), locals(), [mod], -1) + valid = inspect.ismodule(getattr(parent_mod, mod)) + else: + __import__(mod, globals(), locals(), [], -1) + valid = inspect.ismodule(sys.modules[mod]) + if not valid: + if added: + sys.path.pop() + added = False + return logical_line.find(mod), ("NOVA N304: No relative " + "imports. '%s' is a relative import" % logical_line) + + return logical_line.find(mod), ("NOVA N302: import only " + "modules. '%s' does not import a module" % logical_line) + + except (ImportError, NameError): + if not added: + added = True + sys.path.append(current_path) + return importModuleCheck(mod, parent, added) + else: + print >> sys.stderr, ("ERROR: import '%s' failed, couldn't " + "find module" % logical_line) + added = False + sys.path.pop() + return + + except AttributeError: + # Invalid import + return logical_line.find(mod), ("NOVA N303: Invalid import, " + "AttributeError raised") + + split_line = logical_line.split() + + # handle "import x" + # handle "import x as y" + if (logical_line.startswith("import ") and "," not in logical_line and + (len(split_line) == 2 or + (len(split_line) == 4 and split_line[2] == "as"))): + mod = split_line[1] + return importModuleCheck(mod) + + # handle "from x import y" + # handle "from x import y as z" + elif (logical_line.startswith("from ") and "," not in logical_line and + split_line[2] == "import" and split_line[3] != "*" and + (len(split_line) == 4 or + (len(split_line) == 6 and split_line[4] == "as"))): + mod = split_line[3] + return importModuleCheck(mod, split_line[1]) + + # TODO(jogo) handle "from x import *" + +#TODO(jogo) Dict and list objects + +current_file = "" + + +def readlines(filename): + """ + record the current file being tested + """ + pep8.current_file = filename + return open(filename).readlines() + + +def add_nova(): + """ + Look for functions that start with nova_ and have arguments + and add them to pep8 module + Assumes you know how to write pep8.py checks + """ + for name, function in globals().items(): + if not inspect.isfunction(function): + continue + args = inspect.getargspec(function)[0] + if args and name.startswith("nova"): + exec("pep8.%s = %s" % (name, name)) + +if __name__ == "__main__": + #include nova path + sys.path.append(os.getcwd()) + #NOVA error codes start with an N + pep8.ERRORCODE_REGEX = re.compile(r'[EWN]\d{3}') + add_nova() + pep8.current_file = current_file + pep8.readlines = readlines + pep8._main() -- cgit