From bd39b6bb19d66d378d5b5c18a63bc274e5f967ff Mon Sep 17 00:00:00 2001 From: Thomas Thrainer <thomasth@google.com> Date: Mon, 29 Jul 2013 11:38:15 +0200 Subject: [PATCH] Enable pylint and PEP8 for test support code The test support code is mainly written from scratch, so in order to ensure to keep the code quality high, enable pylint and PEP8 checks for it. Due to some specialities of the test code, a dedicate pylintrc-test file is created which configures pylint for test code. Those differences include: - lowercase functions are allowed if the first word is test, assert or main - lowercase methods are allowed if the first word(s) is test, assert, runTests, setUp or tearDown - R0201 (method could be a function) is disabled Signed-off-by: Thomas Thrainer <thomasth@google.com> Reviewed-by: Michele Tartara <mtartara@google.com> --- Makefile.am | 12 +- pylintrc | 2 + pylintrc-test | 113 ++++++++++++++++++ test/py/__init__.py | 2 +- test/py/cmdlib/__init__.py | 2 +- test/py/cmdlib/test_unittest.py | 4 +- test/py/cmdlib/testsupport/__init__.py | 15 +-- test/py/cmdlib/testsupport/cmdlib_testcase.py | 16 ++- test/py/cmdlib/testsupport/config_mock.py | 36 ++++-- test/py/cmdlib/testsupport/iallocator_mock.py | 4 + .../cmdlib/testsupport/lock_manager_mock.py | 4 + test/py/cmdlib/testsupport/processor_mock.py | 17 ++- test/py/cmdlib/testsupport/rpc_runner_mock.py | 12 +- test/py/mocks.py | 13 +- test/py/testutils.py | 5 +- 15 files changed, 212 insertions(+), 45 deletions(-) create mode 100644 pylintrc-test diff --git a/Makefile.am b/Makefile.am index 7cabd8fd6..ce24accc6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -991,6 +991,7 @@ EXTRA_DIST = \ UPGRADE \ epydoc.conf.in \ pylintrc \ + pylintrc-test \ autotools/build-bash-completion \ autotools/build-rpc \ autotools/check-header \ @@ -1451,7 +1452,8 @@ pep8_python_code = \ $(CHECK_HEADER) \ $(DOCPP) \ $(PYTHON_BOOTSTRAP) \ - qa + qa \ + $(python_test_support) test/py/daemon-util_unittest.bash: daemons/daemon-util @@ -1931,7 +1933,7 @@ PEP8_IGNORE = E111,E121,E125,E127,E261,E501 # For excluding pep8 expects filenames only, not whole paths PEP8_EXCLUDE = $(subst $(space),$(comma),$(strip $(notdir $(BUILT_PYTHON_SOURCES)))) -LINT_TARGETS = pylint pylint-qa +LINT_TARGETS = pylint pylint-qa pylint-test if HAS_PEP8 LINT_TARGETS += pep8 endif @@ -1953,6 +1955,12 @@ pylint-qa: $(GENERATED_FILES) cd $(top_srcdir)/qa && \ PYTHONPATH=$(abs_top_srcdir) $(PYLINT) $(LINT_OPTS) \ --rcfile ../pylintrc $(patsubst qa/%.py,%,$(qa_scripts)) +# FIXME: lint all test code, not just the newly added test support +pylint-test: $(GENERATED_FILES) + @test -n "$(PYLINT)" || { echo 'pylint' not found during configure; exit 1; } + cd $(top_srcdir) && \ + PYTHONPATH=.:./test/py $(PYLINT) $(LINT_OPTS) \ + --rcfile=pylintrc-test $(python_test_support) .PHONY: pep8 pep8: $(GENERATED_FILES) diff --git a/pylintrc b/pylintrc index b47abcd77..c6359a86c 100644 --- a/pylintrc +++ b/pylintrc @@ -1,6 +1,8 @@ # Configuration file for pylint (http://www.logilab.org/project/pylint). See # http://www.logilab.org/card/pylintfeatures for more detailed variable # descriptions. +# +# NOTE: Keep this file in sync (as much as possible) with pylintrc-test! [MASTER] profile = no diff --git a/pylintrc-test b/pylintrc-test new file mode 100644 index 000000000..d61735a87 --- /dev/null +++ b/pylintrc-test @@ -0,0 +1,113 @@ +# pylint configuration file tailored to test code. +# +# NOTE: Keep in sync as much as possible with the standard pylintrc file. +# Only a few settings had to be adapted for the test code. + +[MASTER] +profile = no +ignore = +persistent = no +cache-size = 50000 +load-plugins = + +[REPORTS] +output-format = colorized +include-ids = yes +files-output = no +reports = no +evaluation = 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) +comment = yes + +[BASIC] +required-attributes = +# disabling docstring checks since we have way too many without (complex +# inheritance hierarchies) +#no-docstring-rgx = __.*__ +no-docstring-rgx = .* +module-rgx = (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ +# added lower-case names +const-rgx = ((_{0,2}[A-Za-z][A-Za-z0-9_]*)|(__.*__))$ +class-rgx = _?[A-Z][a-zA-Z0-9]+$ +# added lower-case names +function-rgx = (_?((([A-Z]+[a-z0-9]+)|test|assert)([A-Z]+[a-z0-9]*)*)|main|([a-z_][a-z0-9_]*))$ +# add lower-case names, since derived classes must obey method names +method-rgx = (_{0,2}(([A-Z]+[a-z0-9]+)|test|assert)([A-Z]+[a-z0-9]*)*|__.*__|runTests|setUp|tearDown|([a-z_][a-z0-9_]*))$ +attr-rgx = [a-z_][a-z0-9_]{1,30}$ +argument-rgx = [a-z_][a-z0-9_]*$ +variable-rgx = (_?([a-z_][a-z0-9_]*)|(_?[A-Z0-9_]+))$ +inlinevar-rgx = [A-Za-z_][A-Za-z0-9_]*$ +good-names = i,j,k,_ +bad-names = foo,bar,baz,toto,tutu,tata +bad-functions = xrange + +[TYPECHECK] +ignore-mixin-members = yes +zope = no +acquired-members = + +[VARIABLES] +init-import = no +dummy-variables-rgx = _ +additional-builtins = + +[CLASSES] +ignore-iface-methods = +defining-attr-methods = __init__,__new__,setUp + +[DESIGN] +max-args = 15 +max-locals = 50 +max-returns = 10 +max-branchs = 80 +max-statements = 200 +max-parents = 7 +max-attributes = 20 +# zero as struct-like (PODS) classes don't export any methods +min-public-methods = 0 +max-public-methods = 50 + +[IMPORTS] +deprecated-modules = regsub,string,TERMIOS,Bastion,rexec +import-graph = +ext-import-graph = +int-import-graph = + +[FORMAT] +max-line-length = 80 +max-module-lines = 4500 +indent-string = " " + +[MISCELLANEOUS] +notes = FIXME,XXX,TODO + +[SIMILARITIES] +min-similarity-lines = 4 +ignore-comments = yes +ignore-docstrings = yes + +[MESSAGES CONTROL] + +# Enable only checker(s) with the given id(s). This option conflicts with the +# disable-checker option +#enable-checker= + +# Enable all checker(s) except those with the given id(s). This option +# conflicts with the enable-checker option +#disable-checker= +disable-checker=similarities + +# Enable all messages in the listed categories (IRCWEF). +#enable-msg-cat= + +# Disable all messages in the listed categories (IRCWEF). +disable-msg-cat= + +# Enable the message(s) with the given id(s). +#enable-msg= + +# Disable the message(s) with the given id(s). +disable-msg=W0511,R0922,W0201 + +# The new pylint 0.21+ style (plus the similarities checker, which is no longer +# a separate opiton, but a generic disable control) +disable=W0511,R0922,W0201,R0922,R0801,I0011,R0201 diff --git a/test/py/__init__.py b/test/py/__init__.py index 13e3dca01..5e1233974 100644 --- a/test/py/__init__.py +++ b/test/py/__init__.py @@ -18,4 +18,4 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. -"""This module contains all python test code""" \ No newline at end of file +"""This module contains all python test code""" diff --git a/test/py/cmdlib/__init__.py b/test/py/cmdlib/__init__.py index 2c9e5d9a5..5d00d83aa 100644 --- a/test/py/cmdlib/__init__.py +++ b/test/py/cmdlib/__init__.py @@ -18,4 +18,4 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. -"""This module contains all cmdlib unit tests""" \ No newline at end of file +"""This module contains all cmdlib unit tests""" diff --git a/test/py/cmdlib/test_unittest.py b/test/py/cmdlib/test_unittest.py index c391eed82..3658e5ef2 100644 --- a/test/py/cmdlib/test_unittest.py +++ b/test/py/cmdlib/test_unittest.py @@ -19,9 +19,7 @@ # 02110-1301, USA. -"""Tests for LUTest* - -""" +"""Tests for LUTest*""" from ganeti import constants diff --git a/test/py/cmdlib/testsupport/__init__.py b/test/py/cmdlib/testsupport/__init__.py index 69fc33df7..ae64c97ac 100644 --- a/test/py/cmdlib/testsupport/__init__.py +++ b/test/py/cmdlib/testsupport/__init__.py @@ -23,12 +23,13 @@ """ -from cmdlib_testcase import CmdlibTestCase -from config_mock import ConfigMock -from iallocator_mock import CreateIAllocatorMock -from lock_manager_mock import LockManagerMock -from processor_mock import ProcessorMock -from rpc_runner_mock import CreateRpcRunnerMock, RpcResultsBuilder +from cmdlib.testsupport.cmdlib_testcase import CmdlibTestCase +from cmdlib.testsupport.config_mock import ConfigMock +from cmdlib.testsupport.iallocator_mock import CreateIAllocatorMock +from cmdlib.testsupport.lock_manager_mock import LockManagerMock +from cmdlib.testsupport.processor_mock import ProcessorMock +from cmdlib.testsupport.rpc_runner_mock import CreateRpcRunnerMock, \ + RpcResultsBuilder __all__ = ["CmdlibTestCase", "ConfigMock", @@ -37,4 +38,4 @@ __all__ = ["CmdlibTestCase", "LockManagerMock", "ProcessorMock", "RpcResultsBuilder", - ] \ No newline at end of file + ] diff --git a/test/py/cmdlib/testsupport/cmdlib_testcase.py b/test/py/cmdlib/testsupport/cmdlib_testcase.py index 3b3485341..37151e698 100644 --- a/test/py/cmdlib/testsupport/cmdlib_testcase.py +++ b/test/py/cmdlib/testsupport/cmdlib_testcase.py @@ -18,11 +18,15 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. -from config_mock import * -from iallocator_mock import * -from lock_manager_mock import * -from processor_mock import * -from rpc_runner_mock import * + +"""Main module of the cmdlib test framework""" + + +from cmdlib.testsupport.config_mock import ConfigMock +from cmdlib.testsupport.iallocator_mock import CreateIAllocatorMock +from cmdlib.testsupport.lock_manager_mock import LockManagerMock +from cmdlib.testsupport.processor_mock import ProcessorMock +from cmdlib.testsupport.rpc_runner_mock import CreateRpcRunnerMock import testutils @@ -34,6 +38,7 @@ class GanetiContextMock(object): self.rpc = rpc +# pylint: disable=R0904 class CmdlibTestCase(testutils.GanetiTestCase): """Base class for cmdlib tests. @@ -83,3 +88,4 @@ class CmdlibTestCase(testutils.GanetiTestCase): """ self.mcpu.assertLogContainsRegex(expected_regex) + diff --git a/test/py/cmdlib/testsupport/config_mock.py b/test/py/cmdlib/testsupport/config_mock.py index a91d84b82..2fea5a942 100644 --- a/test/py/cmdlib/testsupport/config_mock.py +++ b/test/py/cmdlib/testsupport/config_mock.py @@ -18,7 +18,11 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. -import uuid + +"""Support for mocking the cluster configuration""" + + +import uuid as uuid_module from ganeti import config from ganeti import constants @@ -31,6 +35,7 @@ def _StubGetEntResolver(): return mocks.FakeGetentResolver() +# pylint: disable=R0904 class ConfigMock(config.ConfigWriter): """A mocked cluster configuration with added methods for easy customization. @@ -45,7 +50,7 @@ class ConfigMock(config.ConfigWriter): _getents=_StubGetEntResolver()) def _GetUuid(self): - return str(uuid.uuid4()) + return str(uuid_module.uuid4()) def AddNewNodeGroup(self, uuid=None, @@ -56,7 +61,7 @@ class ConfigMock(config.ConfigWriter): hv_state_static=None, disk_state_static=None, alloc_policy=None, - networks=[]): + networks=None): """Add a new L{objects.NodeGroup} to the cluster configuration See L{objects.NodeGroup} for parameter documentation. @@ -72,6 +77,8 @@ class ConfigMock(config.ConfigWriter): uuid = self._GetUuid() if name is None: name = "mock_group_%d" % group_id + if networks is None: + networks = [] group = objects.NodeGroup(uuid=uuid, name=name, @@ -87,6 +94,7 @@ class ConfigMock(config.ConfigWriter): self.AddNodeGroup(group, None) return group + # pylint: disable=R0913 def AddNewNode(self, uuid=None, name=None, @@ -154,12 +162,12 @@ class ConfigMock(config.ConfigWriter): primary_node=None, os="mocked_os", hypervisor=constants.HT_FAKE, - hvparams={}, - beparams={}, - osparams={}, + hvparams=None, + beparams=None, + osparams=None, admin_state=constants.ADMINST_DOWN, - nics=[], - disks=[], + nics=None, + disks=None, disk_template=constants.DT_DISKLESS, disks_active=False, network_port=None): @@ -182,6 +190,16 @@ class ConfigMock(config.ConfigWriter): primary_node = self._master_node.uuid if isinstance(primary_node, objects.Node): primary_node = self._master_node.uuid + if hvparams is None: + hvparams = {} + if beparams is None: + beparams = {} + if osparams is None: + osparams = {} + if nics is None: + nics = [] + if disks is None: + disks = [] inst = objects.Instance(uuid=uuid, name=name, @@ -241,4 +259,4 @@ class ConfigMock(config.ConfigWriter): pass def _GetRpc(self, address_list): - raise NotImplementedError + raise AssertionError("This should not be used during tests!") diff --git a/test/py/cmdlib/testsupport/iallocator_mock.py b/test/py/cmdlib/testsupport/iallocator_mock.py index e706c1da4..1ff3cdada 100644 --- a/test/py/cmdlib/testsupport/iallocator_mock.py +++ b/test/py/cmdlib/testsupport/iallocator_mock.py @@ -18,6 +18,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. + +"""Support for mocking the IAllocator interface""" + + import mock from ganeti.masterd import iallocator diff --git a/test/py/cmdlib/testsupport/lock_manager_mock.py b/test/py/cmdlib/testsupport/lock_manager_mock.py index 19c65b568..5c5f8f652 100644 --- a/test/py/cmdlib/testsupport/lock_manager_mock.py +++ b/test/py/cmdlib/testsupport/lock_manager_mock.py @@ -18,6 +18,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. + +"""Support for mocking the lock manager""" + + from ganeti import locking diff --git a/test/py/cmdlib/testsupport/processor_mock.py b/test/py/cmdlib/testsupport/processor_mock.py index 0eb707398..9c950612c 100644 --- a/test/py/cmdlib/testsupport/processor_mock.py +++ b/test/py/cmdlib/testsupport/processor_mock.py @@ -18,6 +18,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. + +"""Support for mocking the opcode processor""" + + import re from ganeti import constants @@ -42,6 +46,9 @@ class LogRecordingCallback(mcpu.OpExecCbBase): self.processor.log_entries.append((log_type, log_msg)) + def SubmitManyJobs(self, jobs): + return mcpu.OpExecCbBase.SubmitManyJobs(self, jobs) + class ProcessorMock(mcpu.Processor): """Mocked opcode processor for tests. @@ -89,8 +96,8 @@ class ProcessorMock(mcpu.Processor): """Return a string with all log entries separated by a newline. """ - return "\n".join("%s: %s" % (type, msg) - for type, msg in self.GetLogEntries()) + return "\n".join("%s: %s" % (log_type, msg) + for log_type, msg in self.GetLogEntries()) def GetLogMessagesString(self): """Return a string with all log messages separated by a newline. @@ -107,8 +114,8 @@ class ProcessorMock(mcpu.Processor): @param expected_msg: the expected message """ - for type, msg in self.log_entries: - if type == expected_type and msg == expected_msg: + for log_type, msg in self.log_entries: + if log_type == expected_type and msg == expected_msg: return raise AssertionError( @@ -144,4 +151,4 @@ class ProcessorMock(mcpu.Processor): raise AssertionError( "Could not find '%s' in LU log messages. Log is:\n%s" % (expected_regex, self.GetLogMessagesString()) - ) \ No newline at end of file + ) diff --git a/test/py/cmdlib/testsupport/rpc_runner_mock.py b/test/py/cmdlib/testsupport/rpc_runner_mock.py index 9d9f6473d..23998c527 100644 --- a/test/py/cmdlib/testsupport/rpc_runner_mock.py +++ b/test/py/cmdlib/testsupport/rpc_runner_mock.py @@ -18,6 +18,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. + +"""Support for mocking the RPC runner""" + + import mock from ganeti import objects @@ -91,7 +95,7 @@ class RpcResultsBuilder(object): else: return node.uuid - def CreateSuccessfulNodeResult(self, node, data={}): + def CreateSuccessfulNodeResult(self, node, data=None): """@see L{RpcResultsBuilder} @param node: @see L{RpcResultsBuilder}. @@ -99,6 +103,8 @@ class RpcResultsBuilder(object): @param data: the data as returned by the RPC @rtype: L{rpc.RpcResult} """ + if data is None: + data = {} return rpc.RpcResult(data=(True, data), node=self._GetNodeId(node)) def CreateFailedNodeResult(self, node): @@ -127,7 +133,7 @@ class RpcResultsBuilder(object): """ return rpc.RpcResult(data=(False, error_msg), node=self._GetNodeId(node)) - def AddSuccessfulNode(self, node, data={}): + def AddSuccessfulNode(self, node, data=None): """@see L{CreateSuccessfulNode}""" self._results.append(self.CreateSuccessfulNodeResult(node, data)) return self @@ -150,4 +156,4 @@ class RpcResultsBuilder(object): @rtype: dict """ - return dict((result.node, result) for result in self._results) \ No newline at end of file + return dict((result.node, result) for result in self._results) diff --git a/test/py/mocks.py b/test/py/mocks.py index 21481de7e..d06d6715a 100644 --- a/test/py/mocks.py +++ b/test/py/mocks.py @@ -24,7 +24,6 @@ import os -from ganeti import utils from ganeti import netutils @@ -37,7 +36,7 @@ FAKE_CLUSTER_KEY = ("AAAAB3NzaC1yc2EAAAABIwAAAQEAsuGLw70et3eApJ/ZEJkAVZogIrm" "vYdB2nQds7/+Bf40C/OpbvnAxna1kVtgFHAo18cQ==") -class FakeConfig: +class FakeConfig(object): """Fake configuration object""" def IsCluster(self): @@ -61,7 +60,7 @@ class FakeConfig: def GetMasterNodeName(self): return netutils.Hostname.GetSysName() - def GetDefaultIAllocator(Self): + def GetDefaultIAllocator(self): return "testallocator" def GetNodeName(self, node_uuid): @@ -74,7 +73,7 @@ class FakeConfig: return map(self.GetNodeName, node_uuids) -class FakeProc: +class FakeProc(object): """Fake processor object""" def Log(self, msg, *args, **kwargs): @@ -90,14 +89,14 @@ class FakeProc: pass -class FakeGLM: +class FakeGLM(object): """Fake global lock manager object""" - def list_owned(self, level): + def list_owned(self, _): return set() -class FakeContext: +class FakeContext(object): """Fake context object""" def __init__(self): diff --git a/test/py/testutils.py b/test/py/testutils.py index f0fe57b78..033daa85b 100644 --- a/test/py/testutils.py +++ b/test/py/testutils.py @@ -27,7 +27,6 @@ import stat import tempfile import unittest import logging -import types from ganeti import utils @@ -120,7 +119,7 @@ class GanetiTestCase(unittest.TestCase): while self._temp_files: try: utils.RemoveFile(self._temp_files.pop()) - except EnvironmentError, err: + except EnvironmentError: pass def assertFileContent(self, file_name, expected_content): @@ -207,8 +206,10 @@ def patch_object(*args, **kwargs): """ import mock try: + # pylint: disable=W0212 return mock._patch_object(*args, **kwargs) except AttributeError: + # pylint: disable=E1101 return mock.patch_object(*args, **kwargs) -- GitLab