[Core] Introduce class-based scenarios
This adds support of scenarios implemented as classes in addition to existing method-based scenarios. First class-based scenario will be added in next patch. Implements: class-based-scenarios Change-Id: Ia383226a356cf1b24ac7125c028f547d9de74c0d
This commit is contained in:
parent
8d13116294
commit
8257b4a339
@ -152,12 +152,17 @@ class ScenarioRunner(plugin.Plugin):
|
||||
"""
|
||||
|
||||
def run(self, name, context, args):
|
||||
cls_name, method_name = name.split(".", 1)
|
||||
cls = scenario.Scenario.get(name)._meta_get("cls_ref")
|
||||
scenario_plugin = scenario.Scenario.get(name)
|
||||
|
||||
# NOTE(boris-42): processing @types decorators
|
||||
args = types.preprocess(name, context, args)
|
||||
|
||||
if scenario_plugin.is_classbased:
|
||||
cls, method_name = scenario_plugin, "run"
|
||||
else:
|
||||
cls, method_name = (scenario_plugin._meta_get("cls_ref"),
|
||||
name.split(".", 1).pop())
|
||||
|
||||
with rutils.Timer() as timer:
|
||||
self._run_scenario(cls, method_name, context, args)
|
||||
|
||||
|
@ -18,6 +18,7 @@ import random
|
||||
|
||||
import six
|
||||
|
||||
from rally.common.i18n import _
|
||||
from rally.common import logging
|
||||
from rally.common.objects import task # noqa
|
||||
from rally.common.plugin import plugin
|
||||
@ -33,23 +34,32 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def configure(name=None, namespace="default", context=None):
|
||||
"""Make from plain python method task scenario plugin.
|
||||
"""Configure scenario by setting proper meta data.
|
||||
|
||||
:param name: Plugin name
|
||||
:param namespace: Plugin namespace
|
||||
:param context: Default task context that is created for this scenario.
|
||||
If there are custom user specified contexts this one
|
||||
will be updated by provided contexts.
|
||||
This can also transform plain function into scenario plugin, however
|
||||
this approach is deprecated - now scenarios must be represented by classes
|
||||
based on rally.task.scenario.Scenario.
|
||||
|
||||
:param name: str scenario name
|
||||
:param namespace: str plugin namespace
|
||||
:param context: default task context that is created for this scenario.
|
||||
If there are custom user specified contexts this one
|
||||
will be updated by provided contexts.
|
||||
"""
|
||||
def wrapper(func):
|
||||
plugin.from_func(Scenario)(func)
|
||||
func._meta_init()
|
||||
def wrapper(scen):
|
||||
scen.is_classbased = hasattr(scen, "run") and callable(scen.run)
|
||||
if not scen.is_classbased:
|
||||
plugin.from_func(Scenario)(scen)
|
||||
scen._meta_init()
|
||||
if name:
|
||||
func._set_name_and_namespace(name, namespace)
|
||||
if "." not in name.strip("."):
|
||||
msg = (_("Scenario name must include a dot: '%s'") % name)
|
||||
raise exceptions.RallyException(msg)
|
||||
scen._set_name_and_namespace(name, namespace)
|
||||
else:
|
||||
func._meta_set("namespace", namespace)
|
||||
func._meta_set("default_context", context or {})
|
||||
return func
|
||||
scen._meta_set("namespace", namespace)
|
||||
scen._meta_set("default_context", context or {})
|
||||
return scen
|
||||
return wrapper
|
||||
|
||||
|
||||
|
@ -1662,6 +1662,14 @@ class FakeScenario(scenario.Scenario):
|
||||
raise multiprocessing.TimeoutError()
|
||||
|
||||
|
||||
@scenario.configure(name="classbased.fooscenario")
|
||||
class FakeClassBasedScenario(FakeScenario):
|
||||
"""Fake class-based scenario."""
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class FakeTimer(rally_utils.Timer):
|
||||
|
||||
def duration(self):
|
||||
|
@ -169,6 +169,29 @@ class ScenarioRunnerTestCase(test.TestCase):
|
||||
runner_obj._run_scenario.assert_called_once_with(
|
||||
cls, method_name, context_obj, expected_config_kwargs)
|
||||
|
||||
@mock.patch(BASE + "rutils.Timer.duration", return_value=10)
|
||||
def test_run_classbased(self, mock_timer_duration):
|
||||
scenario_class = fakes.FakeClassBasedScenario
|
||||
runner_obj = serial.SerialScenarioRunner(
|
||||
mock.MagicMock(),
|
||||
mock.MagicMock())
|
||||
runner_obj._run_scenario = mock.Mock()
|
||||
context_obj = {"task": runner_obj.task,
|
||||
"scenario_name": "classbased.fooscenario",
|
||||
"admin": {"credential": "foo_credentials"},
|
||||
"config": {}}
|
||||
|
||||
result = runner_obj.run("classbased.fooscenario", context_obj,
|
||||
{"foo": 11, "bar": "spam"})
|
||||
|
||||
self.assertIsNone(result)
|
||||
self.assertEqual(runner_obj.run_duration,
|
||||
mock_timer_duration.return_value)
|
||||
self.assertEqual([], list(runner_obj.result_queue))
|
||||
|
||||
runner_obj._run_scenario.assert_called_once_with(
|
||||
scenario_class, "run", context_obj, {"foo": 11, "bar": "spam"})
|
||||
|
||||
def test_abort(self):
|
||||
runner_obj = serial.SerialScenarioRunner(
|
||||
mock.MagicMock(),
|
||||
|
@ -30,12 +30,13 @@ class ScenarioConfigureTestCase(test.TestCase):
|
||||
|
||||
def test_configure(self):
|
||||
|
||||
@scenario.configure("test_configure", "testing")
|
||||
@scenario.configure("fooscenario.name", "testing")
|
||||
def some_func():
|
||||
pass
|
||||
|
||||
self.assertEqual("test_configure", some_func.get_name())
|
||||
self.assertEqual("fooscenario.name", some_func.get_name())
|
||||
self.assertEqual("testing", some_func.get_namespace())
|
||||
self.assertFalse(some_func.is_classbased)
|
||||
some_func.unregister()
|
||||
|
||||
def test_configure_default_name(self):
|
||||
@ -47,6 +48,7 @@ class ScenarioConfigureTestCase(test.TestCase):
|
||||
self.assertIsNone(some_func._meta_get("name"))
|
||||
self.assertEqual("testing", some_func.get_namespace())
|
||||
self.assertEqual({"any": 42}, some_func._meta_get("default_context"))
|
||||
self.assertFalse(some_func.is_classbased)
|
||||
some_func.unregister()
|
||||
|
||||
def test_configure_cls(self):
|
||||
@ -62,8 +64,20 @@ class ScenarioConfigureTestCase(test.TestCase):
|
||||
self.assertEqual("any", ScenarioPluginCls.some.get_namespace())
|
||||
self.assertEqual({"any": 43},
|
||||
ScenarioPluginCls.some._meta_get("default_context"))
|
||||
self.assertFalse(ScenarioPluginCls.some.is_classbased)
|
||||
ScenarioPluginCls.some.unregister()
|
||||
|
||||
def test_configure_classbased(self):
|
||||
|
||||
@scenario.configure(name="fooscenario.name", namespace="testing")
|
||||
class SomeScenario(scenario.Scenario):
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
self.assertEqual("fooscenario.name", SomeScenario.get_name())
|
||||
self.assertTrue(SomeScenario.is_classbased)
|
||||
SomeScenario.unregister()
|
||||
|
||||
|
||||
class ScenarioTestCase(test.TestCase):
|
||||
|
||||
|
@ -200,9 +200,6 @@ class FakeClientsScenarioTestCase(ScenarioTestCase):
|
||||
self._fake_clients = fakes.FakeClients()
|
||||
|
||||
|
||||
def get_test_context():
|
||||
return {
|
||||
"task": {
|
||||
"uuid": str(uuid.uuid4())
|
||||
}
|
||||
}
|
||||
def get_test_context(**kwargs):
|
||||
kwargs["task"] = {"uuid": str(uuid.uuid4())}
|
||||
return kwargs
|
||||
|
@ -56,17 +56,18 @@ class DocstringsTestCase(test.TestCase):
|
||||
"One-line description for %s "
|
||||
"should be declarative and not start "
|
||||
"with 'Test(s) ...'" % scenario_inst.get_name())
|
||||
params_count = scenario_inst.__code__.co_argcount
|
||||
params = scenario_inst.__code__.co_varnames[:params_count]
|
||||
documented_params = [p["name"] for p in doc["params"]]
|
||||
for param in params:
|
||||
if param not in ignored_params:
|
||||
self.assertIn(param, documented_params,
|
||||
"Docstring for %(scenario)s should "
|
||||
"describe the '%(param)s' parameter "
|
||||
"in the :param <name>: clause." %
|
||||
{"scenario": scenario_inst.get_name(),
|
||||
"param": param})
|
||||
if not scenario_inst.is_classbased:
|
||||
params_count = scenario_inst.__code__.co_argcount
|
||||
params = scenario_inst.__code__.co_varnames[:params_count]
|
||||
documented_params = [p["name"] for p in doc["params"]]
|
||||
for param in params:
|
||||
if param not in ignored_params:
|
||||
self.assertIn(param, documented_params,
|
||||
"Docstring for %(scenario)s should "
|
||||
"describe the '%(param)s' parameter "
|
||||
"in the :param <name>: clause." %
|
||||
{"scenario": scenario_inst.get_name(),
|
||||
"param": param})
|
||||
|
||||
def test_all_deploy_engines_have_docstrings(self):
|
||||
for deploy_engine in engine.Engine.get_all():
|
||||
|
Loading…
Reference in New Issue
Block a user