diff --git a/setup.py b/setup.py index 2f458abe..fd8a4132 100644 --- a/setup.py +++ b/setup.py @@ -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", diff --git a/src/rotest/management/__init__.py b/src/rotest/management/__init__.py index d5ce12d6..fb88fc5a 100644 --- a/src/rotest/management/__init__.py +++ b/src/rotest/management/__init__.py @@ -7,12 +7,15 @@ class ManagementConfig(AppConfig): def ready(self): from .models import ResourceData from .client.manager import ClientResourceManager - from .base_resource import BaseResource, ResourceRequest + from .base_resource import (BaseResource, + ResourceAdapter, + ResourceRequest) import rotest rotest.management.ResourceData = ResourceData rotest.management.ClientResourceManager = ClientResourceManager rotest.management.BaseResource = BaseResource + rotest.management.ResourceAdapter = ResourceAdapter rotest.management.ResourceRequest = ResourceRequest diff --git a/src/rotest/management/base_resource.py b/src/rotest/management/base_resource.py index 0af6c72e..e4afc485 100644 --- a/src/rotest/management/base_resource.py +++ b/src/rotest/management/base_resource.py @@ -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 @@ -55,12 +55,44 @@ 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 clone(self): - """Create a copy of the request.""" - return ResourceRequest(self.name, self.type, **self.kwargs) + def get_type(self, config): + """Get the requested resource class. + + Args: + config (dict): the configuration file being used. + """ + return self.type + + +class ResourceAdapter(ResourceRequest): + + """Holds the data for a resource request. + + Attributes: + resource_name (str): attribute name to be assigned. + resource_class (type): resource type. + force_initialize (bool): a flag to determine if the resources will be + initialized even if their validation succeeds. + kwargs (dict): requested resource arguments. + """ + + 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 ExceptionCatchingThread(Thread): @@ -158,7 +190,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 diff --git a/src/rotest/management/client/manager.py b/src/rotest/management/client/manager.py index 4de647ec..088f3717 100644 --- a/src/rotest/management/client/manager.py +++ b/src/rotest/management/client/manager.py @@ -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() diff --git a/src/rotest/management/models/ut_resources.py b/src/rotest/management/models/ut_resources.py index 92647874..f32ce413 100644 --- a/src/rotest/management/models/ut_resources.py +++ b/src/rotest/management/models/ut_resources.py @@ -5,7 +5,7 @@ import os import shutil -from rotest.management.base_resource import BaseResource +from rotest.management.base_resource import BaseResource, ResourceAdapter from .ut_models import ResourceData, DemoResourceData, DemoComplexResourceData @@ -158,3 +158,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) diff --git a/tests/core/test_case.py b/tests/core/test_case.py index e31e0ead..fd1c7af5 100644 --- a/tests/core/test_case.py +++ b/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 @@ -11,10 +12,14 @@ from future.utils import iteritems from rotest.core.case import request +from rotest.management.base_resource import ResourceAdapter 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, + NonExistingResource, + DemoComplexResource, + DemoAdaptiveComplexResource) from tests.core.utils import (ErrorInSetupCase, SuccessCase, FailureCase, ErrorCase, StoreMultipleFailuresCase, @@ -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 @@ -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) @@ -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.