From 6d63b102bf36ce197d8de066a17a0cae29e0cf89 Mon Sep 17 00:00:00 2001 From: Staroverov Anton Date: Fri, 3 Feb 2017 16:59:47 +0300 Subject: [PATCH] Modifing of test on docstrings Test checks all plugins/classes on sutisfaction of following requirements: - each not-class based plugin should contain description of class - each class based plugin should contains description of 'run' method - each 'run' method should contains description of input parameters This commit also contains fixes of mistakes in docstrings for several classes Change-Id: Iafafa3f4014191979bd7acf52b2d2474f08cc3b9 --- .../openstack/scenarios/cinder/volumes.py | 2 +- .../scenarios/neutron/loadbalancer_v2.py | 4 +- .../openstack/scenarios/nova/aggregates.py | 2 + .../plugins/openstack/scenarios/vm/vmtasks.py | 1 + tests/unit/doc/test_docstrings.py | 91 +++++++++++++++++++ 5 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 tests/unit/doc/test_docstrings.py diff --git a/rally/plugins/openstack/scenarios/cinder/volumes.py b/rally/plugins/openstack/scenarios/cinder/volumes.py index 428b22ea..4c7a1082 100755 --- a/rally/plugins/openstack/scenarios/cinder/volumes.py +++ b/rally/plugins/openstack/scenarios/cinder/volumes.py @@ -784,7 +784,7 @@ class CreateVolumeAndUpdateReadonlyFlag(cinder_utils.CinderScenario, :param size: volume size (integer, in GB) :param image: image to be used to create volume - :param read_only:The value to indicate whether to update volume to + :param read_only: The value to indicate whether to update volume to read-only access mode :param kwargs: optional args to create a volume """ diff --git a/rally/plugins/openstack/scenarios/neutron/loadbalancer_v2.py b/rally/plugins/openstack/scenarios/neutron/loadbalancer_v2.py index 08eb9630..b87340cc 100755 --- a/rally/plugins/openstack/scenarios/neutron/loadbalancer_v2.py +++ b/rally/plugins/openstack/scenarios/neutron/loadbalancer_v2.py @@ -34,8 +34,8 @@ class CreateAndListLoadbalancers(utils.NeutronScenario): The scenario creates a loadbalancer for every subnet and then lists loadbalancers. - :param loadbalancer_create_args: dict, POST /lbaas/loadbalancers - request options + :param lb_create_args: dict, POST /lbaas/loadbalancers + request options """ lb_create_args = lb_create_args or {} subnets = [] diff --git a/rally/plugins/openstack/scenarios/nova/aggregates.py b/rally/plugins/openstack/scenarios/nova/aggregates.py index 4db32b06..4af62771 100644 --- a/rally/plugins/openstack/scenarios/nova/aggregates.py +++ b/rally/plugins/openstack/scenarios/nova/aggregates.py @@ -70,6 +70,7 @@ class CreateAndDeleteAggregate(utils.NovaScenario): """Create an aggregate and then delete it. This scenario first creates an aggregate and then delete it. + :param availability_zone: The availability zone of the aggregate """ aggregate = self._create_aggregate(availability_zone) self._delete_aggregate(aggregate) @@ -125,6 +126,7 @@ class CreateAndGetAggregateDetails(utils.NovaScenario): """Create an aggregate and then get its details. This scenario first creates an aggregate and then get details of it. + :param availability_zone: The availability zone of the aggregate """ aggregate = self._create_aggregate(availability_zone) self._get_aggregate_details(aggregate) diff --git a/rally/plugins/openstack/scenarios/vm/vmtasks.py b/rally/plugins/openstack/scenarios/vm/vmtasks.py index 9847bd6c..1203a670 100644 --- a/rally/plugins/openstack/scenarios/vm/vmtasks.py +++ b/rally/plugins/openstack/scenarios/vm/vmtasks.py @@ -425,6 +425,7 @@ class DDLoadTest(BootRuncommandDelete): """Boot a server from a custom image, run a command that outputs JSON. Example Script in rally-jobs/extra/install_benchmark.sh + :param command: default parameter from scenario """ command["script_inline"] = BASH_DD_LOAD_TEST return super(DDLoadTest, self).run(command=command, **kwargs) diff --git a/tests/unit/doc/test_docstrings.py b/tests/unit/doc/test_docstrings.py new file mode 100644 index 00000000..481ee833 --- /dev/null +++ b/tests/unit/doc/test_docstrings.py @@ -0,0 +1,91 @@ +# Copyright 2014: Mirantis Inc. +# 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. + +from rally import plugins +from rally.common.plugin import discover +from rally.common.plugin import info +from rally.common.plugin import plugin +from rally.deployment import engine +from rally.deployment.serverprovider import provider +from rally.task import sla +from tests.unit import test + +EXCEPTIONS_DOCSTR = "missed_docstrings.txt" +EXCEPTIONS_FORMAT = "wrong_format.txt" + + +class DocstringsTestCase(test.TestCase): + + + def setUp(self): + super(DocstringsTestCase, self).setUp() + plugins.load() + + self.exceptions = self._open_file( + EXCEPTIONS_DOCSTR) + self._open_file(EXCEPTIONS_FORMAT) + + def _open_file(self, filename): + with open("./tests/unit/doc/%s" % filename) as file: + return (file.read().lower().split()) + + def _check_docstrings(self, msg_buffer): + for plg_cls in plugin.Plugin.get_all(): + if plg_cls.__module__.startswith("rally."): + if plg_cls.get_name().lower() not in self.exceptions: + doc = info.parse_docstring(plg_cls.__doc__) + short_description = doc["short_description"] + if short_description.startswith("Test"): + msg_buffer.append("One-line description for %s" + " should be declarative and not" + " start with 'Test(s) ...'" + % plg_cls.__name__) + if not plg_cls.get_info()["title"]: + msg = ("Class '{}' should have a docstring.") + inst_name = plg_cls.__name__ + msg_buffer.append(msg.format(inst_name)) + + def _check_described_params(self, msg_buffer): + for plg_cls in plugin.Plugin.get_all(): + if plg_cls.get_name().lower() not in self.exceptions: + ignored_params = ["self", "scenario_obj"] + if hasattr(plg_cls, "run"): + code_block = plg_cls.run.__code__ + params_count = code_block.co_argcount + params = code_block.co_varnames[:params_count] + param_data = plg_cls.get_info()["parameters"] + documented_params = [p["name"] for p in param_data] + for param in params: + if param not in ignored_params: + if param not in documented_params: + msg = ("Class: %(class)s Docstring for " + "%(scenario)s should" + " describe the '%(param)s' parameter" + " in the :param : clause." + % {"class": plg_cls.__name__, + "scenario": plg_cls.get_name(), + "param": param}) + msg_buffer.append(msg) + + def test_all_plugins_have_docstrings(self): + + msg_buffer = [] + self._check_docstrings(msg_buffer) + if msg_buffer: + self.fail("\n%s" % "\n".join(msg_buffer)) + + msg_buffer = [] + self._check_described_params(msg_buffer) + if msg_buffer: + self.fail("\n%s" % "\n".join(msg_buffer))