Skip to content

Commit

Permalink
Merge 969a91b into 69040f8
Browse files Browse the repository at this point in the history
  • Loading branch information
UnDarkle committed Aug 14, 2019
2 parents 69040f8 + 969a91b commit 0201191
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 13 deletions.
62 changes: 62 additions & 0 deletions docs/advanced/complex_resources.rst
Expand Up @@ -197,3 +197,65 @@ To activate it, simply write in the class scope of your complex resource:
Or you can point it to a variable which you can set/unset using an entry point
(see :ref:`custom_entry_points` to learn how to add CLI entry points).


Resource adapter
================

Sometimes, you'd want the resource class (in tests or sub-resources) to vary.
For example, if you have a resource that changes behavior according to the
current project or context, but still want the two behaviors to be inter-changable.

This is where the option to create a resource adapter helps you.

Generally, you can derive from the class ``rotest.management.ResourceRequest``
and implement yourself the `get_type` and `__init__` methods in accordance with
you specific needs. In most cases the environmental context you need exists
in the run config file, which is the argument to the `get_type` method.

Example for a resource adapter:

.. code-block:: python
from rotest.management.base_resource import ResourceRequest
class ResourceAdapter(ResourceRequest):
"""Holds the data for a resource request."""
def get_type(self, config):
"""Get the requested resource class.
Args:
config (dict): the configuration file being used.
"""
if config.get('project') == 'A':
return ResourceA
else:
return ResourceB
class AdaptiveTest(TestCase):
res = ResourceAdapter()
This will give the test a resource named 'res' that would be either an instance
of `ResourceA` or of `ResourceB` depending on the value of the field 'project'
in the run config json file.

You can also pass kwargs to the adapter the same way you would to BaseResource.request().

Similarly, you can also declare adaptive sub-resources:

.. code-block:: python
from rotest.management import ResourceAdapter
class AdaptiveResource(BaseResource):
DATA_CLASS = CalculatorData
PARALLEL_INITIALIZATION = True
sub_resource = ResourceAdapter(data=CalculatorData.sub_process)
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -3,7 +3,7 @@
from setuptools import setup, find_packages


__version__ = "7.9.2"
__version__ = "7.10.0"

result_handlers = [
"db = rotest.core.result.handlers.db_handler:DBHandler",
Expand Down
23 changes: 14 additions & 9 deletions src/rotest/management/base_resource.py
Expand Up @@ -4,7 +4,7 @@
responsible for the resource static & dynamic information.
"""
# pylint: disable=too-many-instance-attributes,no-self-use,broad-except
# pylint: disable=protected-access
# pylint: disable=protected-access,unused-argument
from __future__ import absolute_import

import sys
Expand Down Expand Up @@ -42,11 +42,10 @@ class ResourceRequest(object):
kwargs (dict): requested resource arguments.
"""

def __init__(self, resource_name, resource_class, **kwargs):
def __init__(self, resource_name=None, resource_class=None, **kwargs):
"""Initialize the required parameters of resource request."""
self.name = resource_name
self.type = resource_class

self.kwargs = kwargs

def __eq__(self, oth):
Expand All @@ -55,12 +54,18 @@ def __eq__(self, oth):

def __repr__(self):
"""Return a string representing the request."""
return "Request %r of type %r (kwargs=%r)" % (self.name, self.type,
self.kwargs)
return "%s %r of type %r (kwargs=%r)" % (self.__class__.__name__,
self.name,
self.type,
self.kwargs)

def get_type(self, config):
"""Get the requested resource class.
def clone(self):
"""Create a copy of the request."""
return ResourceRequest(self.name, self.type, **self.kwargs)
Args:
config (dict): the configuration file being used.
"""
return self.type


class ExceptionCatchingThread(Thread):
Expand Down Expand Up @@ -158,7 +163,7 @@ class fields, where the 'data' attribute in the declaration points
sub_resources = []
for sub_name, sub_request in get_class_fields(self.__class__,
ResourceRequest):
sub_class = sub_request.type
sub_class = sub_request.get_type(self.config)
actual_kwargs = sub_request.kwargs.copy()
actual_kwargs['config'] = self.config
actual_kwargs['base_work_dir'] = self.work_dir
Expand Down
3 changes: 2 additions & 1 deletion src/rotest/management/client/manager.py
Expand Up @@ -384,7 +384,8 @@ def request_resources(self, requests,
ServerError. resource manager failed to lock resources.
"""
requests = list(requests)
descriptors = [ResourceDescriptor(request.type, **request.kwargs)
descriptors = [ResourceDescriptor(request.get_type(config),
**request.kwargs)
for request in requests]

initialized_resources = AttrDict()
Expand Down
38 changes: 37 additions & 1 deletion src/rotest/management/models/ut_resources.py
Expand Up @@ -5,10 +5,26 @@
import os
import shutil

from rotest.management.base_resource import BaseResource
from rotest.management.base_resource import BaseResource, ResourceRequest
from .ut_models import ResourceData, DemoResourceData, DemoComplexResourceData


class ResourceAdapter(ResourceRequest):
"""Holds the data for a resource request."""
def __init__(self, config_key, resource_classes, **kwargs):
"""Initialize the required parameters of resource request."""
super(ResourceAdapter, self).__init__(None, resource_classes, **kwargs)
self.config_key = config_key

def get_type(self, config):
"""Get the requested resource class.
Args:
config (dict): the configuration file being used.
"""
return self.type[config.get(self.config_key)]


class DemoResource(BaseResource):
"""Fake resource class, used in resource manager tests."""
DATA_CLASS = DemoResourceData
Expand Down Expand Up @@ -158,3 +174,23 @@ def initialize(self):
class DemoService(BaseResource):
"""Fake service class, used in resource manager tests."""
DATA_CLASS = None


class DemoResource2(DemoResource):
"""A demo resource class similar to DemoResource."""


class DemoAdaptiveComplexResource(BaseResource):
"""Fake complex resource class, used in resource manager tests.
Attributes:
sub_res1 (DemoResource): sub resource pointer.
sub_res2 (DemoResource / DemoResource2): sub resource pointer.
"""
DATA_CLASS = DemoComplexResourceData

sub_res1 = DemoResource2.request(data=DATA_CLASS.demo1)
sub_res2 = ResourceAdapter(config_key='field1',
resource_classes={True: DemoResource,
False: DemoResource2},
data=DATA_CLASS.demo2)
118 changes: 117 additions & 1 deletion tests/core/test_case.py
@@ -1,6 +1,7 @@
"""Test Rotest's TestCase class behavior."""
# pylint: disable=missing-docstring,unused-argument,protected-access
# pylint: disable=no-member,no-self-use,too-many-public-methods,invalid-name
# pylint: disable=too-many-lines
from __future__ import absolute_import

import os
Expand All @@ -14,7 +15,11 @@
from rotest.core.models.case_data import TestOutcome, CaseData
from rotest.management.models.ut_models import DemoResourceData
from rotest.management.models.ut_resources import (DemoResource,
NonExistingResource)
DemoResource2,
ResourceAdapter,
NonExistingResource,
DemoComplexResource,
DemoAdaptiveComplexResource)

from tests.core.utils import (ErrorInSetupCase, SuccessCase, FailureCase,
ErrorCase, StoreMultipleFailuresCase,
Expand Down Expand Up @@ -49,6 +54,35 @@ class TempComplexRequestCase(SuccessCase):
res2 = DemoResource.request(name='available_resource2')


class TempComplexAdaptiveResourceCase(SuccessCase):
"""Inherit class and override resources requests."""
__test__ = False

resources = (request('res1', DemoAdaptiveComplexResource),)


class TempAdaptiveRequestPositiveCase(SuccessCase):
"""Inherit class and override resources requests."""
__test__ = False

resources = ()
res = ResourceAdapter(config_key='field1',
resource_classes={False: DemoResource,
True: DemoComplexResource},
name='available_resource1')


class TempAdaptiveRequestNegativeCase(SuccessCase):
"""Inherit class and override resources requests."""
__test__ = False

resources = ()
res = ResourceAdapter(config_key='field1',
resource_classes={True: DemoResource,
False: DemoComplexResource},
name='available_resource1')


class TempInheritRequestCase(TempComplexRequestCase):
"""Inherit one resource requests from the parent class."""
__test__ = False
Expand Down Expand Up @@ -261,6 +295,29 @@ def test_complex_resource_request(self):
self.assertIn('available_resource2', locked_names,
"Resource request using class field ignored kwargs")

def test_complex_adaptive_resource_request(self):
"""Test a TestCase that requests a complex adaptive resources."""
case = self._run_case(TempComplexAdaptiveResourceCase,
config={'field1': False})

self.assertTrue(self.result.wasSuccessful(),
'Case failed when it should have succeeded')

# === Validate case data object ===
self.assertTrue(case.data.success)

test_resources = case.all_resources

self.assertEqual(len(test_resources), 1,
"Unexpected number of resources, expected %r got %r" %
(1, len(test_resources)))

resource, = list(test_resources.values())
self.assertTrue(isinstance(resource.sub_res1, DemoResource2),
"Got wrong resource %r for sub_res1" % resource)
self.assertTrue(isinstance(resource.sub_res2, DemoResource2),
"Got wrong resource %r for sub_res2" % resource)

def test_inherit_resource_request(self):
"""Test a TestCase that inherits its resource request."""
case = self._run_case(TempInheritRequestCase)
Expand All @@ -284,6 +341,65 @@ def test_inherit_resource_request(self):
"Test doesn't contain field named %s" %
request_name)

def test_adaptive_request_positive(self):
"""Test a positive TestCase with a resource adapter.
* Using the kwargs, ask for an existing resource.
"""
case = self._run_case(TempAdaptiveRequestPositiveCase,
config={'field1': False})

self.assertTrue(self.result.wasSuccessful(),
'Case failed when it should have succeeded')

# === Validate case data object ===
self.assertTrue(case.data.success)

test_resources = case.all_resources
locked_names = []
for resource in six.itervalues(test_resources):
self.assertTrue(isinstance(resource, DemoResource),
"Got wrong resource %r for the request" % resource)

test_resource = DemoResourceData.objects.get(name=resource.name)
self.validate_resource(test_resource)
locked_names.append(resource.name)

self.assertEqual(len(test_resources), 1,
"Unexpected number of resources, expected %r got %r" %
(1, len(test_resources)))

request_name = 'res'
self.assertTrue(request_name in test_resources,
"Test didn't request a resource for %s" %
request_name)

self.assertTrue(request_name in case.__dict__,
"Test doesn't contain field named %s" %
request_name)

self.assertIn('available_resource1', locked_names,
"Resource request using 'resources' ignored kwargs")

def test_adaptive_request_negative(self):
"""Test a negative TestCase with a resource adapter.
* Using the kwargs, ask for a non existing resource.
"""
case = self._run_case(TempAdaptiveRequestNegativeCase,
config={'field1': False})

self.assertFalse(self.result.wasSuccessful(),
'Case failed when it should have succeeded')

# === Validate case data object ===
self.assertFalse(case.data.success)

test_resources = case.all_resources
self.assertEqual(len(test_resources), 0,
"Unexpected number of resources, expected %r got %r" %
(0, len(test_resources)))

def test_dynamic_resources_locking(self):
"""Test that cases can dynamically lock resources.
Expand Down

0 comments on commit 0201191

Please sign in to comment.