Skip to content

Commit

Permalink
Merge pull request #69 from gregoil/params_to_class
Browse files Browse the repository at this point in the history
Params to class
  • Loading branch information
osherdp committed Jun 25, 2018
2 parents 8b88d3c + 9a8d7b3 commit 75ac8dd
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 396 deletions.
32 changes: 23 additions & 9 deletions docs/advanced/blocks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,14 @@ Common features (for both flows and blocks)
The resources of a flow will automatically propagate to the components under
it.

#. ``common``: used to set values to blocks or sub-flows, see example in the
`Sharing data`_ section.

#. ``parametrize`` (also ``params``): used to pass values to blocks or
sub-flows, see example in the `Sharing data`_ section.
Note that calling ``parametrize()`` or ``params()`` doesn't actually
instantiate the component, but just saves values to be passed to it when it
will be run.
instantiate the component, but just create a copy of the class and sends
the parameters to its common (overriding previous values).

#. ``mode``: this field can be defined statically in the component's class or
passed to the instance using 'parametrize' (parametrized fields override
Expand Down Expand Up @@ -160,8 +163,8 @@ methods:

* Declaring outputs - see TestBlock's ``outputs`` above.

* Setting initial data to the test flow - you can set initial data to the
components of flows by writing:
* Setting initial data to the test - you can set initial data to the
component and its sub-components by writing:

.. code-block:: python
Expand All @@ -170,11 +173,11 @@ methods:
'other_field': 'abc'}
...
This will inject ``field_name=5`` and ``other_field='abc'`` as fields of the flow and
its components before starting its run, so the blocks would also have access
to those fields.
This is the same as sharing those fields at the beginning of the flow's setUp
method, using ``share_data()``.
This will inject ``field_name=5`` and ``other_field='abc'`` as fields of the
flow and its components before starting its run, so the blocks would also
have access to those fields.
Note that you can also declare a ``common`` dict for blocks, but it's
generally recommended to use default values for inputs instead.

* Using parametrize - you can specify fields for blocks or flows by calling
their 'parametrize' or 'params' class method.
Expand All @@ -194,6 +197,17 @@ methods:
inputs and fields section), and a second ``DemoBlock`` with ``field_name=5``
and ``other_field='abc'`` injected into the block instance (at runtime).

Regarding priorities hierarchy between the methods, it follows two rules:

#. For a single component, calling ``parametrize`` on it overrides the
values set through ``common``.

#. ``common`` and ``parametrize`` of sub-components are stronger than
the values passed by containing hierarchies.
E.g. ``common`` values of a flow are of lower priority than the
``parametrize`` values passed to the blocks under it.


Example
-------

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from setuptools import setup, find_packages

__version__ = "3.0.1"
__version__ = "3.0.2"

result_handlers = [
"db = rotest.core.result.handlers.db_handler:DBHandler",
Expand Down
16 changes: 6 additions & 10 deletions src/rotest/core/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,38 +72,34 @@ class TestBlock(AbstractFlowComponent):
def __init__(self, indexer=count(), base_work_dir=ROTEST_WORK_DIR,
save_state=True, force_initialize=False, config=None,
parent=None, run_data=None, enable_debug=True,
resource_manager=None, skip_init=False, is_main=True,
parameters={}):
resource_manager=None, skip_init=False, is_main=True):

super(TestBlock, self).__init__(parent=parent,
config=config,
indexer=indexer,
is_main=is_main,
run_data=run_data,
skip_init=skip_init,
parameters=parameters,
save_state=save_state,
enable_debug=enable_debug,
base_work_dir=base_work_dir,
force_initialize=force_initialize,
resource_manager=resource_manager)

self.addCleanup(self._share_outputs)
self._set_parameters(override_previous=False, **self.__class__.common)

@classmethod
def get_name(cls, **parameters):
def get_name(cls):
"""Return test name.
This method gets gets instantiation arguments that are passed to the
block via 'parametrize' call, and can be overridden to give unique
names to blocks.
You can override this class method and use values from 'common' to
create a more indicative name for the test.
Returns:
str. test name.
"""
class_name = parameters.get(cls.COMPONENT_NAME_PARAMETER,
cls.__name__)

class_name = cls.common.get(cls.COMPONENT_NAME_PARAMETER, cls.__name__)
method_name = cls.get_test_method_name()
return '.'.join((class_name, method_name))

Expand Down
23 changes: 9 additions & 14 deletions src/rotest/core/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
from rotest.core.block import TestBlock
from rotest.common.config import ROTEST_WORK_DIR
from rotest.core.flow_component import (AbstractFlowComponent, MODE_CRITICAL,
MODE_FINALLY, MODE_OPTIONAL,
ClassInstantiator)
MODE_FINALLY, MODE_OPTIONAL)

assert MODE_FINALLY
assert MODE_CRITICAL
Expand Down Expand Up @@ -71,7 +70,6 @@ class TestFlow(AbstractFlowComponent):
IS_COMPLEX (bool): if this test is complex (may contain sub-tests).
TIMEOUT (number): timeout for flow run, None means no timeout.
"""
common = {}
blocks = ()

TAGS = []
Expand All @@ -83,15 +81,14 @@ class TestFlow(AbstractFlowComponent):
def __init__(self, base_work_dir=ROTEST_WORK_DIR, save_state=True,
force_initialize=False, config=None, indexer=count(),
parent=None, run_data=None, enable_debug=False, is_main=True,
skip_init=False, resource_manager=None, parameters={}):
skip_init=False, resource_manager=None):

super(TestFlow, self).__init__(parent=parent,
config=config,
indexer=indexer,
is_main=is_main,
run_data=run_data,
skip_init=skip_init,
parameters=parameters,
save_state=save_state,
enable_debug=enable_debug,
base_work_dir=base_work_dir,
Expand All @@ -103,9 +100,8 @@ def __init__(self, base_work_dir=ROTEST_WORK_DIR, save_state=True,

self._tests = []
for test_class in self.blocks:
if not ((isinstance(test_class, type) and
issubclass(test_class, (TestBlock, TestFlow))) or
isinstance(test_class, ClassInstantiator)):
if not (isinstance(test_class, type) and
issubclass(test_class, (TestBlock, TestFlow))):

raise TypeError("Blocks under TestFlow must be classes "
"inheriting from TestBlock or TestFlow, "
Expand All @@ -124,7 +120,7 @@ def __init__(self, base_work_dir=ROTEST_WORK_DIR, save_state=True,

self._tests.append(test_item)

self.share_data(override_previous=False, **self.__class__.common)
self._set_parameters(override_previous=False, **self.__class__.common)

if self.is_main:
self._validate_inputs()
Expand Down Expand Up @@ -153,17 +149,16 @@ def _validate_inputs(self, extra_inputs=[]):
fields.extend(block.get_outputs().keys())

@classmethod
def get_name(cls, **parameters):
def get_name(cls):
"""Return test name.
This method gets gets instantiation arguments that are passed to the
block via 'parametrize' call, and can be overridden to give unique
names to blocks.
You can override this class method and use values from 'common' to
create a more indicative name for the test.
Returns:
str. test name.
"""
return parameters.get(cls.COMPONENT_NAME_PARAMETER, cls.__name__)
return cls.common.get(cls.COMPONENT_NAME_PARAMETER, cls.__name__)

def _set_parameters(self, override_previous=True, **parameters):
"""Inject parameters into the component and sub components.
Expand Down
57 changes: 14 additions & 43 deletions src/rotest/core/flow_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,35 +43,6 @@ class BlockOutput(object):
pass


class ClassInstantiator(object):
"""Container that holds instantiation parameters for a flow component."""
def __init__(self, component_class, **parameters):
self.component_class = component_class
self.parameters = parameters

def __call__(self, *args, **kwargs):
"""Instantiate the block with the parameters."""
block = self.component_class(*args,
parameters=self.parameters,
**kwargs)

block._set_parameters(**self.parameters)

return block

def get_name(self, **parameters):
"""Return test name.
Args:
method_name (str): name of the test method.
Returns:
str. test name.
"""
parameters.update(self.parameters)
return self.component_class.get_name(**parameters)


class AbstractFlowComponent(AbstractTest):
"""Define TestBlock, which is a part of a test.
Expand Down Expand Up @@ -126,13 +97,13 @@ class AbstractFlowComponent(AbstractTest):
NO_RESOURCES_MESSAGE = 'Failed to request resources'
PREVIOUS_FAILED_MESSAGE = 'Previous component failed'

common = {}
mode = MODE_CRITICAL

def __init__(self, indexer=count(), base_work_dir=ROTEST_WORK_DIR,
save_state=True, force_initialize=False, config=None,
parent=None, run_data=None, enable_debug=True,
resource_manager=None, skip_init=False, is_main=True,
parameters={}):
resource_manager=None, skip_init=False, is_main=True):

test_method_name = self.get_test_method_name()
super(AbstractFlowComponent, self).__init__(indexer, test_method_name,
Expand All @@ -142,9 +113,8 @@ def __init__(self, indexer=count(), base_work_dir=ROTEST_WORK_DIR,

self._pipes = {}
self.is_main = is_main
self.parameters = parameters

name = self.get_name(**parameters)
name = self.get_name()
core_log.debug("Initializing %r flow-component", name)

core_log.debug("Creating database entry for %r test-block", name)
Expand All @@ -166,7 +136,9 @@ def parametrize(cls, **parameters):
This class method does not instantiate the component, but states
values be injected into it after it would be initialized.
"""
return ClassInstantiator(cls, **parameters)
new_common = cls.common.copy()
new_common.update(**parameters)
return type(cls.__name__, (cls,), {'common': new_common})

# Shortcut
params = parametrize
Expand Down Expand Up @@ -249,11 +221,11 @@ def setup_method_wrapper(*args, **kwargs):

try:
if not self.IS_COMPLEX:
self.share_data(override_previous=False,
**{input_name: value.default
for (input_name, value) in
self.get_inputs().iteritems()
if value.is_optional()})
self._set_parameters(override_previous=False,
**{input_name: value.default
for (input_name, value) in
self.get_inputs().iteritems()
if value.is_optional()})

for pipe_name, pipe_target in self._pipes.iteritems():
setattr(self, pipe_name, getattr(self, pipe_target))
Expand Down Expand Up @@ -369,12 +341,11 @@ def had_error(self):
return self.data.exception_type == TestOutcome.ERROR

@classmethod
def get_name(cls, **parameters):
def get_name(cls):
"""Return test name.
This method gets instantiation arguments that are passed to the
component via 'parametrize' call, and can be overridden to give unique
names to components.
You can override this class method and use values from 'common' to
create a more indicative name for the test.
Returns:
str. test name.
Expand Down

0 comments on commit 75ac8dd

Please sign in to comment.