refactor atomics actions

We decided to still use atomic_actions field.
First step, save new atomic actions format in database,
and convert the new format to old format where the atomic
actions needs to be used.
Blueprint: improve-atomic-actions

Co-Authored-By: chenhb-zte <chen.haibing1@zte.com.cn>

Change-Id: I8aecd5bad319aa61d5f8a20a2c2fe9bacdc0e81a
This commit is contained in:
Andrey Kurilin 2016-09-08 15:48:01 +03:00 committed by chenhb
parent b5c8d4e088
commit 8af32076d0
15 changed files with 271 additions and 144 deletions

View File

@ -41,12 +41,10 @@ class CalculateAtomic(scenario.Scenario, utils.RandomNameGeneratorMixin):
:param number_of_atomics: int number of atomics to run
"""
tmp_name = "tmp_actions"
atomic_inst = atomic.ActionTimerMixin()
calc_atomic_name = "calculate_%s_atomics" % number_of_atomics
with atomic.ActionTimer(self, calc_atomic_name):
for _ in range(number_of_atomics):
with atomic.ActionTimer(self, tmp_name):
with atomic.ActionTimer(atomic_inst, tmp_name):
pass
self._atomic_actions = {
calc_atomic_name: self._atomic_actions[calc_atomic_name]}

View File

@ -410,9 +410,39 @@ class Task(object):
def add_subtask(self, **subtask):
return Subtask(self.task["uuid"], **subtask)
def get_results(self):
def _get_results(self):
return db.task_result_get_all_by_uuid(self.task["uuid"])
def get_results(self):
results = self._get_results()
for result in results:
for itr in result["data"]["raw"]:
itr["atomic_actions"] = self.convert_atomic_actions(
itr["atomic_actions"])
return results
"""TODO(chenhb): Remove this method after replacing old format.
Now we do not convert children actions, because our output
string and report only show one layer actions.
"""
@staticmethod
def convert_atomic_actions(atomic_actions):
"""Convert atomic actions to old format. """
if isinstance(atomic_actions, dict):
return atomic_actions
old_style = collections.OrderedDict()
for action in atomic_actions:
duration = action["finished_at"] - action["started_at"]
if action["name"] in old_style:
name_template = action["name"] + " (%i)"
i = 2
while name_template % i in old_style:
i += 1
old_style[name_template % i] = duration
else:
old_style[action["name"]] = duration
return old_style
@classmethod
def extend_results(cls, results, serializable=False):
"""Modify and extend results with aggregated data.
@ -461,6 +491,8 @@ class Task(object):
atomic = collections.OrderedDict()
for itr in scenario["data"]["raw"]:
itr["atomic_actions"] = cls.convert_atomic_actions(
itr["atomic_actions"])
for atomic_name, duration in itr["atomic_actions"].items():
duration = duration or 0
if atomic_name not in atomic:

View File

@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
import functools
from rally.common import utils
@ -22,7 +21,7 @@ from rally.common import utils
class ActionTimerMixin(object):
def __init__(self):
self._atomic_actions = collections.OrderedDict()
self._atomic_actions = []
def atomic_actions(self):
"""Returns the content of each atomic action."""
@ -48,27 +47,26 @@ class ActionTimer(utils.Timer):
"""
super(ActionTimer, self).__init__()
self.instance = instance
self.name = self._get_atomic_action_name(instance, name)
self.instance._atomic_actions[self.name] = None
self.name = name
self._root = self._find_parent(self.instance._atomic_actions)
self.atomic_action = {"name": self.name,
"children": [],
"started_at": None}
self._root.append(self.atomic_action)
@classmethod
def _get_atomic_action_name(cls, instance, name):
# TODO(boris-42): It was quite bad idea to store atomic actions
# inside {}. We should refactor this in 0.2.0 release
# and store them inside array, that will allow us to
# store atomic actions with the same name
if name not in instance._atomic_actions:
return name
def _find_parent(self, atomic_actions):
if atomic_actions and "finished_at" not in atomic_actions[-1]:
return self._find_parent(atomic_actions[-1]["children"])
else:
return atomic_actions
name_template = name + " (%i)"
i = 2
while name_template % i in instance._atomic_actions:
i += 1
return name_template % i
def __enter__(self):
super(ActionTimer, self).__enter__()
self.atomic_action["started_at"] = self.start
def __exit__(self, type_, value, tb):
super(ActionTimer, self).__exit__(type_, value, tb)
self.instance._atomic_actions[self.name] = self.duration()
self.atomic_action["finished_at"] = self.finish
def action_timer(name):

View File

@ -40,7 +40,7 @@ def format_result_on_timeout(exc, timeout):
"duration": timeout,
"idle_duration": 0,
"output": {"additive": [], "complete": []},
"atomic_actions": {},
"atomic_actions": [],
"error": utils.format_exc(exc)
}
@ -234,7 +234,7 @@ class ScenarioRunner(plugin.Plugin):
_RESULT_SCHEMA = {
"fields": [("duration", float), ("timestamp", float),
("idle_duration", float), ("output", dict),
("atomic_actions", dict), ("error", list)]
("atomic_actions", list), ("error", list)]
}
def _result_has_valid_schema(self, result):
@ -257,15 +257,28 @@ class ScenarioRunner(plugin.Plugin):
"proper_type": proper_type.__name__})
return False
for action, value in result["atomic_actions"].items():
if not isinstance(value, float):
actions_list = copy.deepcopy(result["atomic_actions"])
for action in actions_list:
for key in ("name", "started_at", "finished_at", "children"):
if key not in action:
LOG.warning(
"Task %(uuid)s | Atomic action %(action)s has wrong type "
"'%(type)s', should be 'float'"
"Task %(uuid)s | Atomic action %(action)s "
"missing key '%(key)s'"
% {"uuid": self.task["uuid"],
"action": action,
"type": type(value)})
"key": key})
return False
for key in ("started_at", "finished_at"):
if not isinstance(action[key], float):
LOG.warning(
"Task %(uuid)s | Atomic action %(action)s has "
"wrong type '%(type)s', should be 'float'"
% {"uuid": self.task["uuid"],
"action": action,
"type": type(action[key])})
return False
if action["children"]:
actions_list.extend(action["children"])
for e in result["error"]:
if not isinstance(e, str):

View File

@ -64,32 +64,6 @@ def should_be_overridden(func):
return func
# TODO(andreykurilin): remove _DevNullDict and _ServiceWithoutAtomic when we
# start support inner atomics
class _DevNullDict(dict):
"""Do not keep anything."""
def __setitem__(self, key, value):
pass
class _ServiceWithoutAtomic(object):
def __init__(self, service):
self._service = service
self._atomic_actions = _DevNullDict()
def atomic_actions(self):
return self._atomic_actions
def __getattr__(self, name):
return getattr(self._service, name)
def __str__(self):
return "'%s' without atomic actions" % self._service.__name__
def __repr__(self):
return "<%s>" % str(self)
def method_wrapper(func):
"""Wraps service's methods with some magic
@ -139,9 +113,6 @@ def method_wrapper(func):
raise TypeError(message)
if kwargs.pop("no_atomic", False):
instance = _ServiceWithoutAtomic(instance)
return func(instance, *args, **kwargs)
return wrapper

View File

@ -26,6 +26,7 @@ import six
from rally.common.i18n import _
from rally.common.plugin import plugin
from rally.task import utils
configure = plugin.configure
@ -58,6 +59,11 @@ class SLAChecker(object):
:param iteration: iteration result object
"""
if isinstance(iteration, dict):
atomic_actions = iteration.get("atomic_actions", None)
if isinstance(atomic_actions, list):
iteration["atomic_actions"] = utils.WrapperForAtomicActions(
atomic_actions)
return all([sla.add_iteration(iteration) for sla in self.sla_criteria])
def merge(self, other):

View File

@ -23,6 +23,7 @@ import six
from rally.common.i18n import _
from rally.common import logging
from rally.common import objects
from rally import consts
from rally import exceptions
@ -410,3 +411,38 @@ class ActionBuilder(object):
self._build(binding["action"], times,
*(binding["args"] + args), **dft_kwargs))
return bound_actions
# TODO(andreykurilin): We need to implement some wrapper for atomic actions,
# we can use these wrapper to simulate new and old format.
class WrapperForAtomicActions(list):
LOG_INFO = "Atomic actions format is changed. It is a list now."
def __init__(self, atomic_actions):
super(WrapperForAtomicActions, self).__init__(atomic_actions)
self.__atomic_actions = atomic_actions
self.__old_atomic_actions = objects.Task.convert_atomic_actions(
self.__atomic_actions)
def items(self):
LOG.warning(self.LOG_INFO)
return self.__old_atomic_actions.items()
def get(self, name, default=None):
LOG.warning(self.LOG_INFO)
return self.__old_atomic_actions.get(name, default)
def __iter__(self):
return iter(self.__atomic_actions)
def __len__(self):
return len(self.__atomic_actions)
def __getitem__(self, item):
if isinstance(item, int):
# it is a call to list:
return self.__atomic_actions[item]
else:
LOG.warning(self.LOG_INFO)
return self.__old_atomic_actions[item]

View File

@ -229,13 +229,27 @@ class TaskTestCase(test.TestCase):
@mock.patch("rally.common.objects.task.db.task_result_get_all_by_uuid",
return_value="foo_results")
def test_get_results(self, mock_task_result_get_all_by_uuid):
def test__get_results(self, mock_task_result_get_all_by_uuid):
task = objects.Task(task=self.task)
results = task.get_results()
results = task._get_results()
mock_task_result_get_all_by_uuid.assert_called_once_with(
self.task["uuid"])
self.assertEqual(results, "foo_results")
def test_get_results(self):
task = objects.Task(task=self.task)
task._get_results = mock.MagicMock()
return_value = [{"data": {"raw": [
{"atomic_actions": [
{"name": "some",
"started_at": 1.0,
"finished_at": 2.0,
"children": []}]}]}}]
task._get_results.return_value = return_value
self.assertEqual([{"data": {"raw": [
{"atomic_actions": {"some": 1.0}}]}}],
task.get_results())
@mock.patch("rally.common.objects.task.db.task_update")
def test_set_failed(self, mock_task_update):
mock_task_update.return_value = self.task
@ -310,6 +324,23 @@ class TaskTestCase(test.TestCase):
consts.TaskStatus.SOFT_ABORTING)
)
@ddt.data(
{"atomic_actions": [{"name": "some", "started_at": 1.0,
"finished_at": 2.0, "children": []}],
"expected": {"some": 1.0}},
{"atomic_actions": [{"name": "some", "started_at": 1.0,
"finished_at": 2.0, "children": []},
{"name": "some", "started_at": 2.0,
"finished_at": 3.0, "children": []}],
"expected": {"some": 1.0, "some (2)": 1.0}},
{"atomic_actions": {"some": 1.0},
"expected": {"some": 1.0}}
)
@ddt.unpack
def test_convert_atomic_actions(self, atomic_actions, expected):
self.assertEqual(expected,
objects.Task.convert_atomic_actions(atomic_actions))
class SubtaskTestCase(test.TestCase):

View File

@ -28,7 +28,7 @@ class SerialScenarioRunnerTestCase(test.TestCase):
times = 5
result = {"duration": 10., "idle_duration": 0., "error": [],
"output": {"additive": [], "complete": []},
"atomic_actions": {},
"atomic_actions": [],
"timestamp": 1.}
mock__run_scenario_once.return_value = result
deque_as_queue_inst = mock_deque_as_queue.return_value

View File

@ -58,7 +58,7 @@ class StackTestCase(test.ScenarioTestCase):
@mock.patch("rally.plugins.openstack.services.heat.main.open")
@mock.patch("rally.plugins.openstack.services.heat.main.Stack._wait")
def test_create(self, mock_stack__wait, mock_open, mock_task_atomic):
mock_scenario = mock.MagicMock()
mock_scenario = mock.MagicMock(_atomic_actions=[])
mock_scenario.generate_random_name.return_value = "fake_name"
mock_open().read.return_value = "fake_content"
mock_new_stack = {
@ -87,7 +87,8 @@ class StackTestCase(test.ScenarioTestCase):
@mock.patch("rally.plugins.openstack.services.heat.main.open")
@mock.patch("rally.plugins.openstack.services.heat.main.Stack._wait")
def test_update(self, mock_stack__wait, mock_open, mock_task_atomic):
mock_scenario = mock.MagicMock(stack_id="fake_id")
mock_scenario = mock.MagicMock(
stack_id="fake_id", _atomic_actions=[])
mock_parameters = mock.Mock()
mock_open().read.return_value = "fake_content"
stack = main.Stack(

View File

@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
import mock
from rally.task import atomic
@ -39,8 +37,17 @@ class AtomicActionTestCase(test.TestCase):
with atomic.ActionTimer(inst, "some"):
pass
expected = [("test", 20), ("test (2)", 12), ("some", 4)]
self.assertEqual(collections.OrderedDict(expected),
expected = [{"name": "test",
"started_at": 1,
"finished_at": 21,
"children": [{"name": "test",
"started_at": 3,
"finished_at": 15,
"children": [{"name": "some",
"started_at": 6,
"finished_at": 10,
"children": []}]}]}]
self.assertEqual(expected,
inst.atomic_actions())
@mock.patch("time.time", side_effect=[1, 3])
@ -56,8 +63,8 @@ class AtomicActionTestCase(test.TestCase):
except TestException:
pass
expected = [("test", 2)]
self.assertEqual(collections.OrderedDict(expected),
self.assertEqual([{"name": "test", "children": [],
"started_at": 1, "finished_at": 3}],
inst.atomic_actions())
@mock.patch("time.time", side_effect=[1, 3])
@ -71,7 +78,8 @@ class AtomicActionTestCase(test.TestCase):
inst = Some()
self.assertEqual(5, inst.some_func(2, 3))
self.assertEqual(collections.OrderedDict({"some": 2}),
self.assertEqual([{"name": "some", "children": [],
"started_at": 1, "finished_at": 3}],
inst.atomic_actions())
@mock.patch("time.time", side_effect=[1, 3])
@ -88,7 +96,8 @@ class AtomicActionTestCase(test.TestCase):
inst = TestTimer()
self.assertRaises(TestException, inst.some_func)
self.assertEqual(collections.OrderedDict({"test": 2}),
self.assertEqual([{"name": "test", "children": [],
"started_at": 1, "finished_at": 3}],
inst.atomic_actions())
@mock.patch("time.time", side_effect=[1, 3, 1, 3])
@ -107,20 +116,22 @@ class AtomicActionTestCase(test.TestCase):
inst = TestAtomicTimer()
self.assertEqual(5, inst.some_func(2, 3))
self.assertEqual(collections.OrderedDict({"some": 2}),
self.assertEqual([{"name": "some", "children": [],
"started_at": 1, "finished_at": 3}],
inst.atomic_actions())
inst = TestAtomicTimer()
self.assertEqual(5, inst.some_func(2, 3, atomic_action=False))
self.assertEqual(collections.OrderedDict(),
self.assertEqual([],
inst.atomic_actions())
inst = TestAtomicTimer()
self.assertEqual(5, inst.other_func(2, 3))
self.assertEqual(collections.OrderedDict(),
self.assertEqual([],
inst.atomic_actions())
inst = TestAtomicTimer()
self.assertEqual(5, inst.other_func(2, 3, foo=True))
self.assertEqual(collections.OrderedDict({"some": 2}),
self.assertEqual([{"name": "some", "children": [],
"started_at": 1, "finished_at": 3}],
inst.atomic_actions())

View File

@ -39,7 +39,7 @@ class ScenarioRunnerHelpersTestCase(test.TestCase):
"duration": 100,
"idle_duration": 0,
"output": {"additive": [], "complete": []},
"atomic_actions": {},
"atomic_actions": [],
"error": mock_format_exc.return_value
}
@ -85,7 +85,7 @@ class ScenarioRunnerHelpersTestCase(test.TestCase):
"idle_duration": 0,
"error": [],
"output": {"additive": [], "complete": []},
"atomic_actions": {}
"atomic_actions": []
}
self.assertEqual(expected_result, result)
@ -108,7 +108,7 @@ class ScenarioRunnerHelpersTestCase(test.TestCase):
"description": "Complete description",
"title": "Complete",
"chart_plugin": "BarPlugin"}]},
"atomic_actions": {}
"atomic_actions": []
}
self.assertEqual(expected_result, result)
@ -123,7 +123,7 @@ class ScenarioRunnerHelpersTestCase(test.TestCase):
"timestamp": fakes.FakeTimer().timestamp(),
"idle_duration": 0,
"output": {"additive": [], "complete": []},
"atomic_actions": {}
"atomic_actions": []
}
self.assertEqual(expected_result, result)
self.assertEqual(expected_error[:2],
@ -253,67 +253,97 @@ class ScenarioRunnerTestCase(test.TestCase):
@ddt.data(
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"output": {"additive": [], "complete": []},
"error": ["err1", "err2"], "atomic_actions": {}},
"error": ["err1", "err2"], "atomic_actions": []},
"expected": True},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []},
"atomic_actions": {"foo": 4.2}},
"atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 5.2, "children": []}]},
"expected": True},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": ["a1", "a2"],
"complete": ["c1", "c2"]},
"atomic_actions": {"foo": 4.2}},
"atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 5.2, "children": []}]},
"validate_output_calls": [("additive", "a1"), ("additive", "a2"),
("complete", "c1"), ("complete", "c2")],
"expected": True},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": ["a1", "a2"],
"complete": ["c1", "c2"]},
"atomic_actions": {"foo": 4.2}},
"atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 5.2, "children": []}]},
"validate_output_return_value": "validation error message"},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [42], "output": {"additive": [], "complete": []},
"atomic_actions": {"foo": 4.2}}},
"atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 5.2, "children": []}]}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []},
"atomic_actions": {"foo": 42}}},
"atomic_actions": [{"name": "foo", "started_at": 10,
"finished_at": 52, "children": []}]}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []},
"atomic_actions": {"foo": "non-float"}}},
"atomic_actions": [{"name": "non-float", "started_at": 1.0,
"children": []}]}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []},
"atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 4.0,
"children": [{"name": "foo1",
"started_at": 2.0,
"finished_at": 3.0,
"children": []}]}]},
"expected": True},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []},
"atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 4.0,
"children": [{"name": "foo1",
"started_at": 20,
"finished_at": 30,
"children": []}]}]}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []},
"atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 4.0,
"children": [{"name": "foo1",
"started_at": 2.0,
"finished_at": 3.0}]}]}},
{"data": {"duration": 1, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []},
"atomic_actions": {}}},
"atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []},
"atomic_actions": {}}},
"atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1,
"error": [], "output": {"additive": [], "complete": []},
"atomic_actions": {}}},
"atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": "foo", "output": {"additive": [], "complete": []},
"atomic_actions": {}}},
"atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": []},
"atomic_actions": {}}},
"atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"complete": []},
"atomic_actions": {}}},
"atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {}, "atomic_actions": {}}},
"error": [], "output": {}, "atomic_actions": []}},
{"data": {"timestamp": 1.0, "idle_duration": 1.0, "error": [],
"output": {"additive": [], "complete": []},
"atomic_actions": {}}},
"atomic_actions": []}},
{"data": {"duration": 1.0, "idle_duration": 1.0, "error": [],
"output": {"additive": [], "complete": []},
"atomic_actions": {}}},
"atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "error": [],
"output": {"additive": [], "complete": []},
"atomic_actions": {}}},
"atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"output": {"additive": [], "complete": []},
"atomic_actions": {}}},
"atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "atomic_actions": {}}},
"error": [], "atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []}}},
{"data": []},

View File

@ -14,7 +14,6 @@
import mock
from rally import exceptions
from rally.task import atomic
from rally.task import service
from tests.unit import test
@ -246,42 +245,3 @@ class MethodWrapperTestCase(test.TestCase):
Some().foo(some=2, another=3)
Some().foo(1, some=2, another=3)
self.assertRaises(TypeError, Some().foo, 1, 2)
def test_disabling_atomics(self):
class Some(service.UnifiedService):
def discover_impl(self):
return mock.MagicMock, None
@atomic.action_timer("some")
def foo(slf):
pass
def bar(slf):
pass
some = Some(mock.MagicMock(version="777"))
some.foo(no_atomic=True)
self.assertNotIn("some", some._atomic_actions)
# check that we are working with correct variable
some.foo()
self.assertIn("some", some._atomic_actions)
class ServiceWithoutAtomicTestCase(test.TestCase):
def test_access(self):
class Some(atomic.ActionTimerMixin):
def __getattr__(self, attr):
return self
some_cls = Some()
# add something to atomic actions dict to simplify comparison
# (empty fake dict != not empty _atomic_actions dict)
with atomic.ActionTimer(some_cls, "some"):
pass
wrapped_service = service._ServiceWithoutAtomic(some_cls)
self.assertNotEqual(some_cls.atomic_actions(),
wrapped_service.atomic_actions())
self.assertNotEqual(some_cls._atomic_actions,
wrapped_service._atomic_actions)
self.assertEqual(some_cls, wrapped_service.some_var)

View File

@ -13,10 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
import datetime as dt
from jsonschema import exceptions as schema_exceptions
import mock
import six
from rally import exceptions
from rally.task import utils
@ -524,3 +526,38 @@ class WaitForStatusTestCase(test.TestCase):
utils.wait_for_status,
resource=res, ready_statuses=["ready"],
update_resource=upd, timeout=2, id_attr="uuid")
class WrapperForAtomicActionsTestCase(test.TestCase):
def test_dict_atomic(self):
atomic_actions = collections.OrderedDict(
[("action_1", 1), ("action_2", 2)])
atomic_wrapper = utils.WrapperForAtomicActions(atomic_actions)
self.assertEqual(1, atomic_wrapper["action_1"])
self.assertEqual(2, atomic_wrapper["action_2"])
self.assertEqual(atomic_actions.items(),
atomic_wrapper.items())
self.assertEqual(1, atomic_wrapper.get("action_1"))
self.assertIsNone(atomic_wrapper.get("action_3"))
self.assertEqual(2, len(atomic_wrapper))
def test_list_atomic(self):
atomic_actions = [{"name": "action_1", "started_at": 1,
"finished_at": 2, "children": []},
{"name": "action_2", "started_at": 2,
"finished_at": 4, "children": []}]
atomic_wrapper = utils.WrapperForAtomicActions(atomic_actions)
self.assertEqual(1, atomic_wrapper["action_1"])
self.assertEqual(2, atomic_wrapper["action_2"])
self.assertEqual(
collections.OrderedDict(
[("action_1", 1), ("action_2", 2)]).items(),
atomic_wrapper.items())
self.assertEqual(atomic_actions[0], atomic_wrapper[0])
self.assertEqual(atomic_actions[1], atomic_wrapper[1])
self.assertEqual(1, atomic_wrapper.get("action_1"))
self.assertIsNone(None, atomic_wrapper.get("action_3"))
self.assertEqual(2, len(atomic_wrapper))
self.assertEqual(atomic_actions[0], six.next(iter(atomic_wrapper)))

View File

@ -22,6 +22,7 @@ from oslotest import base
from oslotest import mockpatch
from rally.common import db
from rally.common import objects
from rally import plugins
from tests.unit import fakes
@ -46,7 +47,9 @@ class TestCase(base.BaseTestCase):
plugins.load()
def _test_atomic_action_timer(self, atomic_actions, name):
action_duration = atomic_actions.get(name)
_old_atomic_actions = objects.Task.convert_atomic_actions(
atomic_actions)
action_duration = _old_atomic_actions.get(name)
self.assertIsNotNone(action_duration)
self.assertIsInstance(action_duration, float)